Python manages memory automatically using two complementary mechanisms. Reference counting tracks how many references point to each object and frees objects immediately when the count reaches zero. A cyclic garbage collector runs periodically to find and free groups of objects that reference each other in cycles.
The article on the Python object model introduced the idea that names are labels pointing at objects. This article covers what happens to those objects when no labels remain and how Python prevents memory from leaking due to reference cycles.
Understanding memory management helps you write code that uses memory efficiently, debug leaks, and choose the right data structures for long-running applications.
How reference counting works
Every Python object carries a reference count that tracks how many references point to it. When you create an object, its count starts at one. Assigning a new name, passing the object as an argument, or storing it in a container all increase the count.
When a reference goes out of scope, is reassigned, or is explicitly deleted with del, the count decreases. The moment the count hits zero, Python deallocates the object immediately.
The sys.getrefcount function returns the current reference count. Note that the function call itself adds a temporary reference, so the count is always one higher than expected.
import sys
data = [1, 2, 3]
print(sys.getrefcount(data))
alias = data
print(sys.getrefcount(data))
del alias
print(sys.getrefcount(data))The getrefcount function counts its own argument reference, so the reported count is always one higher than the actual number of external references. Creating and deleting aliases changes the count predictably.
2
3
2Reference counting is deterministic and predictable. Objects are freed as soon as they become unreachable, which means resources held by objects are released promptly without waiting for a garbage collection cycle.
The problem of reference cycles
Reference counting alone cannot handle cycles. When two objects reference each other, their counts never reach zero even when nothing outside the cycle references them.
The simplest cycle is a list that contains itself, but real cycles occur in parent-child relationships, doubly-linked structures, and observer patterns.
import gc
class Node:
def __init__(self, name):
self.name = name
self.child = None
a = Node("A")
b = Node("B")
a.child = b
b.child = a
del a, b
unreachable = gc.collect()
print(unreachable > 0)After deleting a and b, the nodes are unreachable but still reference each other. Calling gc.collect() detects and frees them, so the return value is greater than zero. The exact count varies by environment because it includes every unreachable object the collector finds, not just the two nodes.
TrueThe gc module provides the collect function to trigger collection manually. In normal operation, Python runs the collector automatically when allocation thresholds are exceeded.
Working with the gc module
The gc module gives you control over the cyclic garbage collector. You can disable it, tune thresholds, inspect tracked objects, and get detailed collection statistics.
Disabling the collector is sometimes useful for performance-critical sections. Re-enable it afterward to prevent unbounded memory growth.
gc.disable()
try:
data = [1] * 10_000_000
finally:
gc.enable()The get_objects function returns all objects tracked by the collector. This is useful for debugging but can be slow in large programs.
all_lists = [obj for obj in gc.get_objects() if isinstance(obj, list)]
print(f"Tracked lists: {len(all_lists)}")The garbage collector tracks container objects that can participate in cycles. Immutable objects like integers and strings are not tracked because they cannot form cycles.
Using weak references to prevent cycles
A weak reference lets you refer to an object without increasing its reference count. The object can be garbage collected even while weak references to it exist.
The weakref module provides several tools. The ref function creates a single weak reference. The WeakValueDictionary creates a mapping where values are held weakly.
import weakref
class Cache:
def __init__(self):
self._data = weakref.WeakValueDictionary()
def store(self, key, value):
self._data[key] = value
def get(self, key):
return self._data.get(key)
class Payload:
def __init__(self, value):
self.value = value
cache = Cache()
data = Payload([1, 2, 3])
cache.store("result", data)
print(cache.get("result").value)
del data
print(cache.get("result"))After deleting the only strong reference to data, the cache entry disappears automatically. The weak reference did not prevent collection. Note that plain lists, dicts, and other built-in types cannot be weakly referenced directly; only custom class instances and a handful of built-ins support it, which is why the cached value here is a small wrapper class.
[1, 2, 3]
NoneWeak references are ideal for caches, observer patterns, and any situation where you need to track objects without owning them.
The del finalizer and its limitations
The del method is called when an object is about to be destroyed. It is intended for cleanup, but there are significant limitations with cycles.
Since Python 3.4, the cyclic collector can safely finalize and free objects in a cycle even when more than one of them defines del. The real limitation is order: you cannot control which object's del runs first, so del methods should not depend on the state of other objects in the same cycle.
class Resource:
def __del__(self):
print("cleanup")
r = Resource()
del rThe single object is collected normally because there is no reference cycle keeping it alive. The del finalizer method runs immediately and prints its cleanup message before deallocation.
cleanupFor reliable resource cleanup, prefer context managers with the with statement over del. Context managers guarantee that exit runs at a predictable time regardless of how the block is exited.
The article on weak references in Python covers more advanced weakref patterns, including finalization callbacks that work correctly with reference cycles.
Rune AI
Key Insights
- Python uses reference counting for immediate deallocation and a cycle detector for circular references.
- The gc module provides collection control, threshold tuning, and leak debugging.
- Reference cycles prevent objects from being freed by reference counting alone.
- Weak references (weakref module) let you reference objects without keeping them alive.
- The del finalizer is unpredictable in cycles; prefer context managers for resource cleanup.
Frequently Asked Questions
How does Python's reference counting work?
What are weak references and when should I use them?
Conclusion
Python's memory management combines deterministic reference counting with a cyclic garbage collector for handling reference cycles. The gc module gives you visibility and control over collection. Weak references let you build caches and registries that do not create leaks. Understanding these mechanisms helps you write code that manages memory efficiently and debug issues when they arise.
More in this topic
How to Test Python Code
Learn what testing means in Python, the core ideas behind automated tests, and the built-in and third-party tools that make testing practical.
Write Your First Python Unit Test
Write and run your first Python unit test step by step, using unittest to check a small function and understand the test output.
The Factory Pattern in Python
Learn how to use the factory pattern in Python to encapsulate object creation, making your code more flexible and easier to extend.