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