Learning

Memory Management & GC

Memory Management & GC

Reference Counting

python
import sys a = [1, 2, 3] print(sys.getrefcount(a)) # 2 (a + temporary arg) b = a print(sys.getrefcount(a)) # 3 (a, b, temporary) del b print(sys.getrefcount(a)) # 2 del a # refcount → 0, memory freed immediately

Garbage Collector for Cycles

python
import gc import sys class Node: def __init__(self, name): self.name = name self.ref = None def __repr__(self): return f'Node({self.name})' # Create a reference cycle a = Node('A') b = Node('B') a.ref = b b.ref = a print(sys.getrefcount(a)) # Still referenced! del a del b # Refcount never reaches 0 — cycle! # But GC handles it collected = gc.collect() print(f'Collected {collected} objects') # 2

gc Module

python
import gc print(f'GC enabled: {gc.isenabled()}') gc.disable() print(f'GC enabled: {gc.isenabled()}') gc.enable() # Force collection gc.collect() # Get garbage objects (unreachable with cycles) print(f'Garbage count: {len(gc.garbage)}') # Set debug flags gc.set_debug(gc.DEBUG_STATS)

Memory Profiling

python
import sys # Object sizes print(sys.getsizeof(0)) # 28 bytes print(sys.getsizeof('hello')) # 54 bytes print(sys.getsizeof([1,2,3])) # 88 bytes print(sys.getsizeof({})) # 64 bytes print(sys.getsizeof(())) # 40 bytes (tuple is smaller than list!) # Tracing memory allocations import tracemalloc tracemalloc.start() # Allocate memory data = [list(range(1000)) for _ in range(100)] # Get current memory snapshot snapshot = tracemalloc.take_snapshot() for stat in snapshot.statistics('lineno')[:3]: print(stat) tracemalloc.stop()

del Method

python
class Resource: def __init__(self, name): self.name = name print(f'{self.name} created') def __del__(self): print(f'{self.name} destroyed') r = Resource('MyResource') # 'MyResource created' del r # 'MyResource destroyed' # Warning: __del__ not guaranteed to run! # Use context managers instead for reliable cleanup

Object Interning

python
# Small integers are interned (-5 to 256) a = 100 b = 100 print(a is b) # True (same object!) a = 300 b = 300 print(a is b) # False (different objects!) # Short strings may be interned a = 'hello' b = 'hello' print(a is b) # True (implementation detail, don't rely on it!) # Force interning import sys a = sys.intern('a_very_long_string_that_wouldnt_normally_be_interned') b = sys.intern('a_very_long_string_that_wouldnt_normally_be_interned') print(a is b) # True
Key Rules
  • •Python uses reference counting as PRIMARY memory management — objects are freed immediately when refcount hits 0
  • •Reference cycles (A→B→A) are handled by the cyclic garbage collector — runs periodically, not immediately
  • •Use sys.getrefcount(obj) to check reference count (note: the call itself adds 1 temporary reference)
  • •sys.getsizeof() returns the size of the CONTAINER, not including referenced objects recursively
  • •Avoid __del__ for cleanup — it's unreliable (may not run, can create uncollectable cycles). Use context managers.
  • •Small integers (-5 to 256) and some strings are interned (cached/reused) — don't rely on 'is' for value equality
Your Task

Create a `Node` class with `name` and `ref` attributes. Create a reference cycle (A.ref = B, B.ref = A), delete both variables, then use `gc.collect()` to clean up and print how many objects were collected. Use `sys.getrefcount()` to demonstrate refcount changes when creating and deleting references. Use `sys.getsizeof()` to compare sizes of list vs tuple vs set with the same elements. Use `tracemalloc` to measure memory before and after creating a large list.

EditorPython · JSX
PreviewUpdates on Run Tests
Loading preview…
Tests
Should define Node class with name and ref
Should use sys.getrefcount()
Should create reference cycle and use gc.collect()
Should compare sizes with sys.getsizeof
Should use tracemalloc.start() and stop()
Should import sys, gc, and tracemalloc