Weak Attributes June 04, 2006
Weak attributes are attributes of an instance that hold only a weak reference to another object.
They are very useful for automatic breaking of cyclic references. This weakattr
class
implements a data-descriptor that holds a weak reference to the attribute, so that when it’s no
longer strongly-referenced, it automatically “disappears” from the instance.
The motivation for this concept comes from http://mail.python.org/pipermail/python-3000/2006-June/002357.html
Code
import weakref
class weakattr ( object ):
"""
weakattr - a weakly-referenced attribute. When the attribute is no longer
referenced, it 'disappears' from the instance. Great for cyclic references.
"""
__slots__ = [ "dict" , "errmsg" ]
def __init__ ( self , name = None ):
self . dict = weakref . WeakValueDictionary ()
if name :
self . errmsg = "%%r has no attribute named %r" % ( name ,)
else :
self . errmsg = "%r has no such attribute"
def __repr__ ( self ):
return "<weakattr at 0x%08X>" % ( id ( self ),)
def __get__ ( self , obj , cls ):
if obj is None :
return self
try :
return self . dict [ id ( obj )]
except KeyError :
raise AttributeError ( self . errmsg % ( obj ,))
def __set__ ( self , obj , value ):
self . dict [ id ( obj )] = value
def __delete__ ( self , obj ):
try :
del self . dict [ id ( obj )]
except KeyError :
raise AttributeError ( self . errmsg % ( obj ,))
Example
A simple example
>>> class x(object):
... cyc = weakattr()
...
... def __init__(self):
... self.cyc = self
... def __del__(self):
... print "g'bye"
... print hasattr(self, "cyc") # will print False
...
>>> y = x()
>>> y
<__main__.x object at 0x009EEAF0>
>>> y.cyc
<__main__.x object at 0x009EEAF0>
>>> y.cyc.cyc
<__main__.x object at 0x009EEAF0>
>>> del y
>>> gc.collect() # force a collection
g'bye # printed by y.__del__
False # printed by y.__del__
0 # return value of gc.collect
[[code]]
In case you wondered what the optional parameter means
[[code]]
>>> class x(object):
... attr1 = weakattr()
... attr2 = weakattr("attr2")
...
>>>
>>> y = x()
>>> # will not show a name
...
>>> y.attr1
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 24, in __get__
AttributeError: <__main__.x object at 0x009EEDD0> has no such attribute
>>>
>>> # will show 'attr2'
...
>>> y.attr2
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 24, in __get__
AttributeError: <__main__.x object at 0x009EEDD0> has no attribute named 'attr2'
>>>
And a somewhat more complex one:
>>> class Node(object):
... next = weakattr()
...
... def __init__(self, name):
... self.name = name
... def __del__(self):
... print "g'bye", self
... def __repr__(self):
... return "<Node %s>" % (self.name,)
...
>>>
>>> node1 = Node("node1")
>>> node2 = Node("node2")
>>> node1.next = node2
>>> node2.next = node1
>>>
>>> node1
<Node node1>
>>> node1.next
<Node node2>
>>> node1.next.next
<Node node1>
>>> node1.next.next.next
<Node node2>
>>>
>>> del node2
>>> # forcibly collect the dead objects
... # this will cause node1.next to disappear, so that
... # node2._del__ is be called
>>> gc.collect()
g'bye <Node node2>
0