Circular references and the weakref module
In the cases where we need circular references but also want __del__()
to work nicely, we can use weak references. One common use case for circular references are mutual references: a parent with a collection of children; each child has a reference back to the parent. If a Player
class has multiple hands, it might be helpful for a Hand
object to contain a reference to the owning Player
class.
The default object references could be called strong references; however, direct references is a better term. They're used by the reference-counting mechanism in Python and can be discovered by the garbage collector if reference counting can't remove the objects. They cannot be ignored.
A strong reference to an object is followed directly. Consider the following statement:
When we say:
a= B()
The a
variable has a direct reference to the object of the B
class that was created. The reference count to the instance of B
is at least 1 because the a
variable has a reference.
A weak reference involves a two-step process to find the associated object. A weak reference will use x.parent()
, invoking the weak reference as a callable object to track down the actual parent object. This two-step process allows the reference counting or garbage collection to remove the referenced object, leaving the weak reference dangling.
The weakref
module defines a number of collections that use weak references instead of strong references. This allows us to create dictionaries that, for example, permit the garbage collection of otherwise unused objects.
We can modify our Parent
and Child
classes to use weak references from Child
to Parent
, permitting a simpler destruction of unused objects.
The following is a modified class that uses weak references from Child
to Parent
:
import weakref class Parent2: def __init__( self, *children ): self.children= list(children) for child in self.children: child.parent= weakref.ref(self) def __del__( self ): print( "Removing {__class__.__name__} {id:d}".format( __class__=self.__class__, id=id(self)) )
We've changed the child to parent reference to be a weakref
object reference.
From within a Child
class, we must locate the parent
object via a two-step operation:
p = self.parent() if p is not None: # process p, the Parent instance else: # the parent instance was garbage collected.
We can explicitly check to be sure the referenced object was found. There's a possibility that the reference was left dangling.
When we use this new Parent2
class, we see that reference counting goes to zero and the object is removed:
>>> p = Parent2( Child(), Child() ) >>> del p Removing Parent2 4303253584 Removing Child 4303256464 Removing Child 4303043344
When a weakref
reference is dead (because the referent was destroyed), we have three potential responses:
- Recreate the referent. Reload it from a database, perhaps.
- Use the
warnings
module to write the debugging information on low-memory situations where the garbage collector removed objects unexpectedly. - Ignore the problem.
Generally, the weakref
references are dead because objects have been removed: variables have gone out of scope, a namespace is no longer in use, the application is shutting down. For this reason, the third response is quite common. The object trying to create the reference is probably about to be removed as well.