Weak references

I've been living off of them for years, but have finally found a use for Python's weak references in my own code.

In Shapely, coordinate sequences, rings of polygons, and members of multi-part geometries provide proxy access to a GEOS geometry structure "owned" by the parent polygon or multi-part geometry. Through their lives they must carry references to the parent like this:

>>> point.coords.__p__ == point
True

so that an anonymous parent object isn't swept up by the Python garbage collector before a hobuism like:

>>> len(polygon.exterior.centroid.coords)
1

executes completely. The implementation is a bit tricky, but a Shapely user doesn't need to think about it: it just works.

Some sub-geometries are expensive enough to create that I've decided to cache them in the parent. Since the "child" already holds a reference to the parent, making a reference to the child from the parent creates a potentially problematic reference cycle. The example below shows cyclic references that prevent Python's automatic garbage collection from freeing objects. The garbage attribute of the gc object below contains references to objects that aren't freed.

>>> import gc
>>>
>>> class ReferencingThing:
...     ref = None
...
>>> gc.enable()
>>> gc.set_debug(gc.DEBUG_SAVEALL)
>>>
>>> for i in range(2):
...     a = ReferencingThing()
...     b = ReferencingThing()
...     a.ref = b
...     b.ref = a
...
>>> gc.collect()
4
>>> gc.garbage
[{'ref': <__main__.ReferencingThing instance at 0xb7dbec4c>},
 <__main__.ReferencingThing instance at 0xb7dbeb8c>,
 {'ref': <__main__.ReferencingThing instance at 0xb7dbeb8c>},
 <__main__.ReferencingThing instance at 0xb7dbec4c>
 ]

A Shapely user demonstrated to me such a bug that was causing geometries to pile up in his application. A weak reference, which is not enough to keep an object alive, does not so block Python's automatic garbage collection. Here is the non-leaking weakly-referencing version of the code above:

>>> import gc
>>> import weakref
>>>
>>> class WeaklyReferencingThing:
...     _ref = None
...     def reference(self, ob):
...         self._ref = weakref.ref(ob)
...     def dereference(self):
...         return self._ref()
...
>>> gc.enable()
>>> gc.set_debug(gc.DEBUG_SAVEALL)
>>>
>>> for i in range(2):
...     p = WeaklyReferencingThing()
...     q = WeaklyReferencingThing()
...     p.reference(q)
...     q.reference(p)
...
>>> gc.collect()
0
>>> gc.garbage
[]

I've also come across a pattern for weak attributes that provides a nicer syntax.