Technical notes

Tracked objects

The snapshot helper function returns a snapshot of all live Python objects that are currently being tracked by the cyclic garbage collector; that’s not the same thing as all currently live Python objects.

In more detail: not all Python objects are tracked by the cyclic garbage collector. Objects like integers and strings have no references to other objects, so cannot be involved in a cycle; so they’re not tracked. There’s a gc.is_tracked function in the standard library that allows you to find out whether an object is currently tracked or not:

>>> import gc
>>> a = 12.56
>>> b = [1, 2, 3]
>>> gc.is_tracked(a)
False
>>> gc.is_tracked(b)
True

There are some surprises lurking, though:

>>> a = 1, 2, 3
>>> gc.is_tracked(a)
True
>>> gc.collect()
0
>>> gc.is_tracked(a)
False

In current Python, there are some optimizations that untrack certain objects when it’s clear that they can’t be involved in a cycle. Those optimizations run during garbage collection. In the example above, the garbage collector determined that none of the objects in the tuple a was tracked, so that there’s no need to track a either.

This means that you shouldn’t depend on the set of tracked objects being too predictable. When diagnosing object leaks, it’s tempting to compare lengths of gc.get_objects return values before and after some computation, but since gc.get_objects returns only the gc-tracked objects, those lengths may change depending on the optimizations. For example:

>>> import gc
>>> a = (((1, 2, 3), 4), (5, 6))
>>> len(gc.get_objects())
3586
>>> gc.collect()
0
>>> len(gc.get_objects())
3410
>>> gc.collect()
0
>>> len(gc.get_objects())
3409
>>> gc.collect()
0
>>> len(gc.get_objects())
3408
>>> gc.collect()
0
>>> len(gc.get_objects())
3408
>>> gc.collect()
0

Referrers versus referents

The ObjectGraph class fills in edges using gc.get_referents, rather than gc.get_referrers. There’s an important difference between the two functions: not only is gc.get_referrers much slower than gc.get_referents; it would also return a smaller graph. Python objects that are trackable by the garbage collector provide a natural mechanism for computing their referents (via the tp_traverse slot at C level), but there’s no simple way to find the referrers of a given object. So gc.get_referrers(obj) traverses the entire list of current gc-tracked objects looking for those which refer to obj. This is slower, and returns only gc-tracked objects.