8.23 循环引用数据结构的内存管理

    一个简单的循环引用数据结构例子就是一个树形结构,双亲节点有指针指向孩子节点,孩子节点又返回来指向双亲节点。这种情况下,可以考虑使用 库中的弱引用。例如:

    这种是想方式允许parent静默终止。例如:

    1. >>> root = Node('parent')
    2. >>> c1 = Node('child')
    3. >>> root.add_child(c1)
    4. >>> print(c1.parent)
    5. Node('parent')
    6. >>> del root
    7. >>> print(c1.parent)
    8. None
    9. >>>

    循环引用的数据结构在Python中是一个很棘手的问题,因为正常的垃圾回收机制不能适用于这种情形。例如考虑如下代码:

    1. # Class just to illustrate when deletion occurs
    2. class Data:
    3. def __del__(self):
    4. print('Data.__del__')
    5.  
    6. # Node class involving a cycle
    7. class Node:
    8. def __init__(self):
    9. self.parent = None
    10. self.children = []
    11.  
    12. def add_child(self, child):
    13. self.children.append(child)
    14. child.parent = self

    可以看到,最后一个的删除时打印语句没有出现。原因是Python的垃圾回收机制是基于简单的引用计数。当一个对象的引用数变成0的时候才会立即删除掉。而对于循环引用这个条件永远不会成立。因此,在上面例子中最后部分,父节点和孩子节点互相拥有对方的引用,导致每个对象的引用计数都不可能变成0。

    Python有另外的垃圾回收器来专门针对循环引用的,但是你永远不知道它什么时候会触发。另外你还可以手动的触发它,但是代码看上去很挫:

    1. >>> import gc
    2. >>> gc.collect() # Force collection
    3. Data.__del__
    4. Data.__del__
    5. >>>

    如果循环引用的对象自己还定义了自己的 方法,那么会让情况变得更糟糕。假设你像下面这样给Node定义自己的 方法:

    1. # Node class involving a cycle
    2. class Node:
    3. def __init__(self):
    4. self.data = Data()
    5. self.parent = None
    6. self.children = []
    7.  
    8. def add_child(self, child):
    9. self.children.append(child)
    10.  
    11. # NEVER DEFINE LIKE THIS.
    12. # Only here to illustrate pathological behavior
    13. def __del__(self):
    14. del self.data
    15. del.parent
    16. del.children

    弱引用消除了引用循环的这个问题,本质来讲,弱引用就是一个对象指针,它不会增加它的引用计数。你可以通过 来创建弱引用。例如:

    1. >>> import weakref
    2. >>> a = Node()
    3. >>> a_ref = weakref.ref(a)
    4. >>> a_ref
    5. <weakref at 0x100581f70; to 'Node' at 0x1005c5410>
    6. >>>

    为了访问弱引用所引用的对象,你可以像函数一样去调用它即可。如果那个对象还存在就会返回它,否则就返回一个None。由于原始对象的引用计数没有增加,那么就可以去删除它了。例如;

    1. >>> print(a_ref())
    2. <__main__.Node object at 0x1005c5410>
    3. >>> del a
    4. Data.__del__
    5. >>> print(a_ref())
    6. None
    7. >>>

    通过这里演示的弱引用技术,你会发现不再有循环引用问题了,一旦某个节点不被使用了,垃圾回收器立即回收它。你还能参考8.25小节关于弱引用的另外一个例子。