python中的弱引用weakref

今天在看python cookbook时遇到一个有趣的问题,特作小记,有知道这个问题原因的兄弟请不吝赐教.

首先,python中的垃圾回收机制是简单的基于引用计数规则的,这一点很好理解,当一个实例对象长时间没人使用时,gc就会自动把它回收掉。不知道那些技术牛的程序员如何看待Python、Java这些自动内存管理的编程语言,但我从C——C++——Python的路线走过来,还是喜欢自己管理内存,手动free\delete\del,不过大型程序架构里这种做法确实特别容易出现问题,特别是在跟你合作的是猪队友的情况下。。。

言归正传,今天我们来看看Python的内存管理不work的情况。大部分情况下Python都可以很好给程序员解决内存问题:

下面的程序是在一个终端中运行:

# 垃圾回收实验
class Data:
    
    def __del__(self):
        print('Data.__del__ ')

>>> a = Node()
>>> del a
Data.__del__

但是当我们处理稍微复杂一点的数据结构,如双向链表、树、图时,我们可能会遇到下面这种蛋疼的事:

# Node class involving a cycle
class Node:
    def __init__(self):
        self.data = Data()
        self.parent = None
        self.children = []

    def add_child(self, child):
        self.children.append(child)
        child.parent = self

>>> a = Node()
>>> del a 
Data.__del__

>>> a = Node()
>>> a.add_child(Node())
>>> del a        
# what???, 没执行__del__()???

可以看到当处理有子节点的树结构时,我们的自动内存管理dosn't work!  原因是python的垃圾回收仅仅是基于引用计数,而在树形结构中父子节点之间相互存在引用关系,所以造成死锁,不会真的删除对象实例。

不过我们可以强制给它回收咯,接上一个code snippet:

>>> import gc
>>> gc.collect()
Data.__del__ 
Data.__del__ 

但是这样写好像很挫哎,不能给我的每个runable file都在代码最后加上这一句吧。

所以,我们引用Python中的weakref包,看看如下代码的结果:

>>> import weakref

>>> a = Node()
#通过 weakref 来创建弱引用
>>> a_weakref = weakref.ref(a)
>>> print(a_weakref)


# 为了访问弱引用所引用的对象,你可以像函数一样去调用它即可。如果那个对象还存在就会返回它,否则就返回一个None
>>> print(a_weakref())
<__main__.Node object at 0x7fd3504129e8>

>>> del a
>>> print(a_weakref())
None

弱引用消除了引用循环的这个问题,本质来讲,弱引用就是一个对象指针,它不会增加它的引用计数.

重写所有代码如下, 代码在vscode中运行:

import weakref

class Node:
    def __init__(self, value):
        self.value = value
        self._parent = None
        self.children = []

    def __repr__(self):
        return 'Node({!r:})'.format(self.value)

    # property that manages the parent as a weak-reference
    @property
    def parent(self):
        return None if self._parent is None else self._parent()

    @parent.setter
    def parent(self, node):
        self._parent = weakref.ref(node)

    def add_child(self, child):
        self.children.append(child)
        child.parent = self

# Example
root = Node('parent')
c1 = Node('child')

root.add_child(c1)
print(c1.parent)  
#输出 Node('parent')

del root
print(c1.parent)    #使parent静默终止
#输出 None

# Class just to illustrate when deletion occurs
class Data:
    
    def __del__(self):
        print('Data.__del__ ')

# Node class involving a cycle
class Node:
    def __init__(self):
        self.data = Data()
        self.parent = None
        self.children = []

    def add_child(self, child):
        self.children.append(child)
        child.parent = self

a1 = Data()
del a1
#输出 Data.__del__

a2 = Node()
del a2 
#输出 Data.__del__

a3 = Node()
a3.add_child(Node())
del a3              
# 还是什么都没输出。。。。。。

a = Node()
a_weakref = weakref.ref(a)
print(a_weakref)
# 输出 

print(a_weakref())
#输出 <__main__.Node object at 0x7fd3504129e8>

del a
print(a_weakref())
#输出 None

# Data.__del__
# Data.__del__

看到最后有意思了,文末彩蛋来了:代码中没有使用gc.collect(), 最后居然执行了两次Data.__Del__() !!!  很明显是del a3对象时没完成的工作!

我的理解是vscode给Python的调试环境添加了一些清尾操作,例如gc.collect()之类,但是我在python debug配置文件--launch.json中并没有找到调用gc的代码,哪位兄弟熟悉vscode的希望可以为小弟指点迷津。

你可能感兴趣的:(Python)