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) # TrueRemember
- CPython deallocates an object when its reference count reaches zero
sys.getrefcountincludes the temporary reference from its own argumentsys.internmakes equal strings share one object; never rely onisfor strings
Watch out for
- Using
isto compare strings because it happened to work for short literals.