python 中的循环引用

Circular reference in python

我不确定python如何处理循环引用(reference cycle)。我检查了一些答案,发现 this:

Python's standard reference counting mechanism cannot free cycles, so the structure in your example would leak.
The supplemental garbage collection facility, however, is enabled by default and should be able to free that structure, if none of its components are reachable from the outside anymore and they do not have __del__() methods.

我想这意味着如果引用循环中的 none 个实例在外部可以访问,则它们都将被清理。这是真的吗?
另一方面,有一个包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

(注意b是如何先删除,然后才能删除a)

并且您可以手动删除循环(如果您可以跟踪它们):

# 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

注意 b 是如何被删除的 after a.b 被删除了。


† 从 Python 3.4 开始,由于 PEP 442 发生了变化。 __del__ 甚至可以在引用循环中的对象上调用并且语义略有不同,因此成为不可回收对象稍微困难一些。

A weakref 仍然有帮助,因为它对垃圾收集器的强度较低,并且可以更早地回收内存。

(试图回答为什么我们会有弱引用子问题。)

Weakrefs 不仅可以破坏循环引用,还可以防止不需要的非循环引用。

我最喜欢的示例是使用 WeakSet 计算并发网络连接数(一种负载测量)。在此示例中,每个新连接都必须添加到 WeakSet,但这是网络代码需要完成的唯一任务。连接可以由服务器、客户端或错误处理程序关闭,但这些例程中的 none 负责从集合中删除连接,这是因为附加引用很弱。

变量是内存引用。

 my_var=10 

这存储在其中一个内存插槽中。 my_var实际引用的是存放10的内存槽地址。如果您键入:

id(my_var) 

您将获得以 10 进制表示的插槽地址。 hex(id(my_var) ) 将给出地址的十六进制表示。

每当我们使用 my_var python 内存管理器进入内存并检索值 10。 Python 内存管理器还跟踪此内存槽的引用数.如果没有对该内存地址的引用,python内存管理器会销毁该对象,并将该内存槽用于新对象。

假设我们有两个 classes:

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))))

当您定义 class A 的实例时:

   my_var = A()

你会得到这样的打印:(在你的系统中,你会有不同的地址)

B: self: 0x1fc1eae44e0, a: 0x1fc1eae4908
A: self: 0x1fc1eae4908, b:0x1fc1eae44e0

注意参考文献。它们是循环引用的。

注意:为了查看这些引用,您必须禁用垃圾收集器,否则它会自动删除它们。

  gc.disable()

当前 my_var 的引用计数 (0x1fc1eae4908) 为 2。my_var 和 classB 正在引用此地址。如果我们改变 my_var

   my_var= None

现在 my_var 没有指向同一个内存地址。现在 (0x1fc1eae4908) 的引用计数是 1 因此这个内存槽没有被清理。

现在我们将遇到内存泄漏,即当不再需要的内存未被清理时。

垃圾收集器会自动识别循环引用中的内存泄漏并进行清理。但是即使循环引用中的一个对象有一个析构函数del()),垃圾收集器并不知道该对象的析构顺序对象。所以对象被标记为uncollectable并且循环引用中的对象没有被清理从而导致内存泄漏.

weakref 用于缓存目的。我认为 python 有很好的文档。

这里是 weakref 的参考:

weakref documentation