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.