Python 的标准引用计数机制无法释放环,因此您示例中的结构将会泄漏。 然而,补充垃圾收集设施已启用,默认情况下应该能够释放该结构,前提是它的所有组件都不再由外部可达并且它们没有 __del__() 方法。
我猜这意味着如果引用循环中的任何实例在外部都不可达,则两者都将被清理。这是真的吗?
另一方面,有一个名为 weakref 的包经常用于处理映射字典。我认为它存在的目的是避免引用循环。
总之,Python 能否自动处理引用循环?如果可以,为什么我们还要使用 weakref?
__del__
方法,那么您不必担心引用循环问题,因为Python可以(并且会)以任何顺序销毁这些对象。如果您的自定义方法确实有__del__
方法,则Python不知道一个对象的删除是否会影响其他对象的删除。例如,当删除一个对象时,它设置了一些全局变量,因此对象仍然存在。您可以创建一个打印输出内容的__del__
方法进行快速测试:class Deletor(str):
def __del__(self):
print(self, 'has been deleted')
a = Deletor('a') # refcount: 1
del a # refcount: 0
输出:
a has been deleted
但是如果你有这样的代码:
a = Deletor('a') # refcount: 1
a.circular = a # refcount: 2
del a # refcount: 1
由于 Python 无法安全删除 a
,因此它不会输出任何内容。它变成了"不可回收对象",可以在gc.garbage
中找到。
这里有两个解决方案,其中一个是使用 weakref
(它不会增加引用计数):
# refcount: a b
a = Deletor('a') # 1 0
b = Deletor('b') # 1 1
b.a = a # 2 1
a.b = weakref.ref(b) # 2 1
del a # 1 1
del b # 1 0
# del b kills b.a # 0 0
输出:
b has been deleted
a has been deleted
# refcount a b
a = Deletor('a') # 1 0
b = Deletor('b') # 1 1
b.a = a # 2 1
a.b = b # 2 2
del b # 2 1
print('del b')
del a.b # 2 0
# b is deleted, now dead
# b.a now dead # 1 0
print('del a.b')
del a # 0 0
print('del a')
输出:
del b
b has been deleted
del a.b
a has been deleted
del a
a.b
之后,b
才会被删除。
__del__
,并且语义略有不同,因此变成无法收集的对象稍微困难一些。
weakref
仍然有用,因为它对垃圾回收器的影响较小,并且可以更早地回收内存。(试图回答为什么我们需要弱引用的子问题。)
弱引用不仅可以打破循环引用,还可以防止不必要的非循环引用。
我最喜欢的例子是使用 WeakSet
计算同时网络连接数(一种负载测量)。在这个例子中,每个新连接都必须添加到 WeakSet
中,但这是网络代码需要完成的唯一任务。连接可以由服务器、客户端或错误处理程序关闭,但是这些例程都没有责任从集合中删除连接,这是因为附加的引用是弱引用。
my_var=10
这个值被存储在内存槽中。 my_var
实际上引用了存储数字10的内存槽地址。如果您键入:
id(my_var)
您将获得基于十进制的插槽地址。 hex(id(my_var))
将给出地址的十六进制表示。
每当我们使用 my_var
时,Python 内存管理器会访问内存并检索值为 10 的变量。Python 内存管理器还会跟踪此内存插槽的引用数量。如果没有对该内存地址的引用,则 Python 内存管理器会销毁该对象,并将此内存插槽用于新对象。
假设我们有两个类:
class A:
def __init__(self):
self.b = B(self)
print('A: self: {0}, b:{1}'.format(hex(id(self)), hex(id(self.b))))
class B:
def __init__(self, a):
self.a = a
print('B: self: {0}, a: {1}'.format(hex(id(self)), hex(id(self.a))))
当你定义类A的一个实例时:
my_var = A()
B: self: 0x1fc1eae44e0, a: 0x1fc1eae4908
A: self: 0x1fc1eae4908, b:0x1fc1eae44e0
注意参考文献。它们存在循环引用。
注意:为了查看这些引用,您需要禁用垃圾收集器,否则它会自动删除它们。
gc.disable()
目前,指针(0x1fc1eae4908)的引用计数为 2。my_var 和 classB 都在引用此地址。如果我们更改 my_var
,则...
my_var= None
现在my_var
指向的内存地址不同了。现在(0x1fc1eae4908)的引用计数为1,因此这个内存槽没有被清除。
现在我们将会有内存泄漏,即当不再需要内存时,它没有被清理。
垃圾回收器会自动识别循环引用中的内存泄漏并清理它们。但是如果循环引用中的任何一个对象具有析构函数 (del()),垃圾回收器就无法知道对象的销毁顺序。因此,该对象被标记为不可回收,循环引用中的对象将不会被清理,从而导致内存泄漏。
weakref
用于缓存目的。我认为Python有非常好的文档。
这里是weakref
的参考资料: