Skip to content

Reference Counting

In languages like C or C++, developers must manually allocate and deallocate memory. This is error-prone and leads to memory leaks or crashes. Python handles this automatically through a system called Reference Counting, supplemented by a cyclic garbage collector.

At its simplest, Python keeps a running tally of how many “labels” (variables) are attached to every object in memory. When no labels remain, the object is immediately destroyed.


Recall from our chapter on Variables that a variable is not a box, but a label pointing to an object. Each of these “points” is a reference.

Every object in Python (defined by the PyObject C struct) has a hidden field called ob_refcnt. This integer tracks the number of references currently pointing to that object.


Python manages the reference count automatically as your code executes.

  1. Assignment: a = [1, 2, 3] (Count becomes 1).
  2. Shared Reference: b = a (Count becomes 2).
  3. Argument Passing: Passing a into a function (Count increases inside the function’s scope).
  4. Container Storage: Adding a to a list or dictionary.
  1. Scope Exit: A local variable goes out of scope when a function returns.
  2. Reassignment: a = 42 (The name a is moved to a new object; the old object’s count drops).
  3. Manual Deletion: Using the del statement (e.g., del a).
  4. Container Destruction: If a list containing an object is deleted, the count of that object drops.

You can inspect the current reference count of an object using the sys module.

ref_count_demo.py
import sys
# 1. Create a list object
x = [1, 2, 3]
print(f"Initial: {sys.getrefcount(x)}")
# 2. Add a reference
y = x
print(f"Shared: {sys.getrefcount(x)}")
# 3. Add to a container
data = [x]
print(f"In List: {sys.getrefcount(x)}")

Output:

Initial: 2
Shared: 3
In List: 4

Reference counting is elegant and fast, but it has one fatal weakness: Circular References.

Imagine two objects that point to each other, but no variable in your program points to either of them.

cycle_demo.py
class Node:
def __init__(self):
self.partner = None
a = Node()
b = Node()
# Create the cycle
a.partner = b
b.partner = a
# Delete our variables
del a
del b

Even after del a and del b, the objects still exist in memory!

  • Object A has a reference count of 1 (held by Object B).
  • Object B has a reference count of 1 (held by Object A).

Since the counts are not zero, reference counting alone will never delete them. This is why Python includes a secondary system: the Generational Garbage Collector (GC), which specifically hunts for these isolated “islands” of objects.


5. Under the Hood: Py_INCREF and Py_DECREF

Section titled “5. Under the Hood: Py_INCREF and Py_DECREF”

If you look at the C source code for the Python interpreter, you will see two macros used everywhere: Py_INCREF(op) and Py_DECREF(op).

  • Py_INCREF simply increments the ob_refcnt.
  • Py_DECREF decrements the count and immediately checks if it has reached zero. If it has, it calls the object’s “destructor” (tp_dealloc) to free the memory.

Why does Python use reference counting instead of just a standard garbage collector like Java?

  1. Immediacy (Determinism): Objects are destroyed the instant they are no longer needed. This makes memory usage more predictable.
  2. Efficiency: For the vast majority of objects, no complex “stop-the-world” scanning is required.
  3. Simplicity: It is a very straightforward way to manage memory in a language where everything is an object.