Reference counts and interning

sys · Expert · 12 min

Memory leaks, object lifetimes and identity surprises make sense once you see the reference counts.

CPython frees most objects through reference counting: each object records how many references point to it, and when the count drops to zero it is deallocated immediately. Cycles are cleaned up separately by the garbage collector.

sys.getrefcount(obj) returns the current count. It is always at least one higher than you expect, because passing obj as the argument creates a temporary reference. Watching the count change is a quick way to see which operations keep objects alive: appending to a list, storing in a dict, capturing in a closure.

Since 3.12, objects like None, True, small ints and interned strings are immortal (PEP 683): their count is fixed at a huge sentinel and never changes, which avoids cache-line writes across threads. Don't use refcounts for such objects, and don't rely on exact values in general; free-threaded 3.13 builds use biased, deferred counting.

Interning stores one canonical copy of a string. Identifiers and many compile-time constants are interned automatically. sys.intern(s) interns a runtime string, so equal interned strings are the same object and dictionary lookups can short-circuit on identity. It can save memory when millions of repeated tokens (log levels, column names) are kept around.

Never use is to compare strings in normal code; interning is an optimisation, not a guarantee.

Example

import sys

data = object()
base = sys.getrefcount(data)
holder = [data, data]
print(sys.getrefcount(data) - base)        # 2
del holder
print(sys.getrefcount(data) - base)        # 0

print(sys.getrefcount(None) > 10**9)       # True: immortal since 3.12

a = "".join(["log", "-", "level"])         # built at runtime
b = "".join(["log", "-", "level"])
print(a == b, a is b)                      # True False
a, b = sys.intern(a), sys.intern(b)
print(a is b)                              # True

Remember

  • CPython deallocates an object when its reference count reaches zero
  • sys.getrefcount includes the temporary reference from its own argument
  • sys.intern makes equal strings share one object; never rely on is for strings

Watch out for

  • Using is to compare strings because it happened to work for short literals.

Check yourself

  1. Why does sys.getrefcount(x) right after x = object() usually return 2, not 1?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/sys.html#sys.getrefcount