Mastering Objectoriented Python
上QQ阅读APP看书,第一时间看更新

Circular references and garbage collection

Here's a common situation that involves circularity. One class, Parent, contains a collection of children. Each Child instance contains a reference to the Parent class.

We'll use these two classes to examine circular references:

class Parent:
    def __init__( self, *children ):
        self.children= list(children)
        for child in self.children:
            child.parent= self
    def __del__( self ):
        print( "Removing {__class__.__name__} {id:d}".format( __class__=self.__class__, id=id(self)) )

class Child:
    def __del__( self ):
        print( "Removing {__class__.__name__} {id:d}".format( __class__=self.__class__, id=id(self)) )

A Parent instance has a collection of children as a simple list.

Each Child instance has a reference to the Parent class that contains it. The reference is created during initialization when the children are inserted into the parent's internal collection.

We've made both classes rather noisy so we can see when the objects are removed. The following is what happens:

>>>> p = Parent( Child(), Child() )
>>> id(p)
4313921808
>>> del p

The Parent and two initial Child instances cannot be removed. They both contain references to each other.

We can create a childless Parent instance, as shown in the following code snippet:

>>> p= Parent()
>>> id(p)
4313921744
>>> del p
Removing Parent 4313921744

This is deleted, as expected.

Because of the mutual or circular references, a Parent instance and its list of Child instances cannot be removed from the memory. If we import the garbage collector interface, gc, we can collect and display these nonremovable objects.

We'll use the gc.collect() method to collect all the nonremovable objects that have a __del__() method, as shown in the following code snippet:

>>> import gc
>>> gc.collect()
174
>>> gc.garbage
[<__main__.Parent object at 0x101213910>, <__main__.Child object at 0x101213890>, <__main__.Child object at 0x101213650>, <__main__.Parent object at 0x101213850>, <__main__.Child object at 0x1012130d0>, <__main__.Child object at 0x101219a10>, <__main__.Parent object at 0x101213250>, <__main__.Child object at 0x101213090>, <__main__.Child object at 0x101219810>, <__main__.Parent object at 0x101213050>, <__main__.Child object at 0x101213210>, <__main__.Child object at 0x101219f90>, <__main__.Parent object at 0x101213810>, <__main__.Child object at 0x1012137d0>, <__main__.Child object at 0x101213790>]

We can see that our Parent objects (for example, ID of 4313921808 = 0x101213910) are prominent on the list of nonremovable garbage. To reduce the reference counts to zero, we would need to either update each Parent instance on the garbage list to remove the children, or update each Child instance on the list to remove the reference to the Parent instance.

Note that we can't break the circularity by putting code in the __del__() method. The __del__() method is called after the circularity has been broken and the reference counts are already zero. When we have circular references, we can no longer rely on simple Python reference counting to clear out the memory of unused objects. We must either explicitly break the circularity or use a weakref reference, which permits garbage collection.