今天在看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的希望可以为小弟指点迷津。