Memory Management and the `gc` Module
Python · Advanced · Run with python3
Explanation
CPython uses reference counting as its primary GC mechanism — an object is freed as soon as its reference count drops to zero. A cycle detector handles reference cycles (objects pointing to each other). The `gc` module controls the cycle collector: `gc.collect()` forces a collection, `gc.disable()` turns it off, `gc.get_threshold()` returns collection thresholds. `weakref` creates references that don't increase the reference count — useful for caches and observer patterns without preventing garbage collection.
Real-World Scenario
A cache holding 10,000 objects: if the cache holds strong references, the objects never get garbage collected. Using `weakref.WeakValueDictionary` instead means the cache automatically shrinks when objects are no longer used elsewhere — no manual eviction needed. ```python import gc import weakref import sys class Node: def __init__(self, value): self.value = value self.next: "Node | None" = None # Reference cycle — would leak without cycle GC a = Node("A") b = Node("B") a.next = b b.next = a # Cycle! print(f"GC before collect: {gc.get_count()}") # (counts objects in each gen) del a, b collected = gc.collect() print(f"Cycle GC collected {collected} objects") # weakref — cache that doesn't prevent GC class ExpensiveObject: def __init__(self, key): self.key = key cache: weakref.WeakValueDictionary = weakref.WeakValueDictionary() obj = ExpensiveObject("config") cache["config"] = obj print(cache.get("config")) # <ExpensiveObject ...> del obj gc.collect() print(cache.get("config")) # None — auto-evicted ```
Quick Snippet
import gc
import weakref
import sys
class Node:
def __init__(self, value):
self.value = value
self.next: "Node | None" = None
# Reference cycle — would leak without cycle GC
a = Node("A")
b = Node("B")
a.next = b
b.next = a # Cycle!
print(f"GC before collect: {gc.get_count()}") # (counts objects in each gen)
del a, b
collected = gc.collect()
print(f"Cycle GC collected {collected} objects")
# weakref — cache that doesn't prevent GC
class ExpensiveObject:
def __init__(self, key): self.key = key
cache: weakref.WeakValueDictionary = weakref.WeakValueDictionary()
obj = ExpensiveObject("config")
cache["config"] = obj
print(cache.get("config")) # <ExpensiveObject ...>
del obj
gc.collect()
print(cache.get("config")) # None — auto-evictedFull Example
# Compile: python3 memory_management.py
# Output:
# Cycle GC collected objects
# None (auto-evicted)
import gc
import weakref
class Node:
def __init__(self, value):
self.value = value
self.next = None
a = Node("A")
b = Node("B")
a.next = b
b.next = a # Reference cycle
del a, b
collected = gc.collect()
print(f"Cycle GC collected {collected} objects")
# weakref — cache that doesn't prevent GC
class ExpensiveObject:
def __init__(self, key): self.key = key
def __repr__(self): return f"ExpensiveObject({self.key!r})"
cache: weakref.WeakValueDictionary = weakref.WeakValueDictionary()
obj = ExpensiveObject("config")
cache["config"] = obj
print(f"In cache: {cache.get('config')}")
del obj
gc.collect()
print(f"After del: {cache.get('config')}") # None
# Reference counting basics
import sys
x = [1, 2, 3]
print(f"Ref count for x: {sys.getrefcount(x)}") # 2 (x + getrefcount arg)
y = x
print(f"Ref count after y=x: {sys.getrefcount(x)}") # 3
del y
print(f"Ref count after del y: {sys.getrefcount(x)}") # 2
python3 memory_management.py