Object Model, Scopes & Memory Internals
Understand LEGB scope lookup rules, dynamic namespaces, reference counting pointers, shallow vs deep cloning, and CPython's generational Garbage Collector (`gc`).
Interactive Sandbox Studio: 4 Tracks & 26 Competencies
Switch tracks and select any of the 26 core topics below to run production Python code, inspect CPython memory mechanics, and execute unit test assertion suites in real time.
CPython Memory Architecture & References
- Variables are Labels, Not Boxes: In CPython, variables point to heap-allocated `PyObject` structs. Immutable objects (`int`, `str`, `tuple`) cannot be changed in place; mutating them creates a new object memory address.
- Memory Management Mechanics: CPython manages memory using immediate **Reference Counting** supplemented by a **3-Generation Cyclic Garbage Collector** (`gc`) that detects and destroys reference cycles.
1. Namespaces & LEGB Scope Resolution
Python resolves variable names in strict order: Local → Enclosing → Global → Built-in. Use `nonlocal` to bind variables in enclosing nested closures.
# Global Scope
config_setting = "PRODUCTION"
def outer_service():
# Enclosing Scope
request_count = 0
def inner_counter():
nonlocal request_count # Bind to enclosing variable
request_count += 1
return request_count
return inner_counter
counter = outer_service()
print("Call 1:", counter()) # 1
print("Call 2:", counter()) # 2
2. Shallow vs Deep Copying (`copy.copy` vs `copy.deepcopy`)
A shallow copy (`copy.copy()`) creates a new outer container but copies references to nested objects. A deep copy (`copy.deepcopy()`) recursively clones all nested objects.
import copy
original = [1, 2, [3, 4]]
# Shallow Copy: Outer list is copied, inner list is shared reference
shallow = copy.copy(original)
shallow[2].append(99)
print("Original after shallow mutation:", original) # [1, 2, [3, 4, 99]]
# Deep Copy: Independent recursive clone
deep = copy.deepcopy(original)
deep[2].append(100)
print("Original after deep mutation:", original) # [1, 2, [3, 4, 99]]
print("Deep object:", deep) # [1, 2, [3, 4, 99, 100]]
3. Reference Counting & Garbage Collection (`sys.getrefcount`, `gc`)
Every `PyObject` tracks its reference count. When the count reaches zero, memory is freed immediately. Cyclic references are reclaimed by CPython's generational GC scanner.
import sys
import gc
data = ["heavy_payload_block"]
print("Initial Ref Count:", sys.getrefcount(data) - 1) # 1
alias = data
print("Ref Count after alias:", sys.getrefcount(data) - 1) # 2
del alias
print("Ref Count after del alias:", sys.getrefcount(data) - 1) # 1
# Manually inspect & trigger Garbage Collection
collected = gc.collect()
print("GC Collected Objects:", collected)