I wrote a class in Python with num_instances = 0 and expected it to reset every time a new object was created.
It didn't. Here's why β and it completely changed how I think about memory. π§
The code that confused me
class ObjectCounter:
num_instances = 0 # I thought this resets every time
def __init__(self):
ObjectCounter.num_instances += 1
obj1 = ObjectCounter() # num_instances = 1
obj2 = ObjectCounter() # num_instances = 2
# Never reset to 0. Why??
My assumption was wrong. num_instances = 0 does NOT run on every object creation. It runs once β when Python first reads the class definition.
Think of the class body like a blueprint being drawn once. The
__init__is the construction happening each time. The blueprint is never redrawn.
Diagram 1 β What actually happens step by step
[ Class defined ] βββΊ [ obj1 = ObjectCounter() ] βββΊ [ obj2 = ObjectCounter() ]
num_instances = 0 __init__ runs __init__ runs
runs ONCE here count = 1 count = 2
lives in class memory mutates class attribute counter keeps growing
vs a function:
num = 0 β created fresh on every call β dies when function returns β always resets to 0
Diagram 2 β Stack vs Heap: where things actually live
βββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββ
β STACK MEMORY β β HEAP MEMORY β
β Function calls & locals β β Classes, objects, lists, dicts β
β β β β
β βββββββββββββββββββββββ β β ββββββββββββββββββββββββββββββββ β
β β main() β β β β ObjectCounter (class object) β β
β β x, y, z β born & β β β β __dict__: {num_instances: 2} β β
β β die here β β β β Stays alive entire program β β
β βββββββββββββββββββββββ β β ββββββββββββ¬βββββββββββ¬ββββββββββ β
β β β β² β² β
β βββββββββββββββββββββββ β β βββββββββββ΄βββ βββββ΄βββββββββ β
β β my_function() β β β β obj1 β β obj2 β β
β β num = 0 β β β β instance β β instance β β
β β reset every call β β β βββββββββββββββ ββββββββββββββ β
β βββββββββββββββββββββββ β β β
β β β β
β β temporary β β β permanent (until GC) β
β β dies when fn returns β β β shared across all instances β
β β no shared state β β β mutated in place, never reset β
βββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββ
Diagram 3 β The mind-bending part: variables are just labels
In Python, a variable doesn't hold a value. It's just a label pointing to an object on the heap. Even x = 5 β that integer is a full heap object with its own memory address.
Stack (names) Heap (objects)
βββββββββββββ
β a = ββββββΌβββββββββββΊ βββββββββββββββββββββββ
βββββββββββββ ββββββΊβ list: [1, 2, 3, 4] β
βββββββββββββ β β ref count = 2 β
β b = ββββββΌβββββββ βββββββββββββββββββββββ
βββββββββββββ
b.append(4) also changes a!
Both names point to the same object.
Diagram 4 β Python's secret: integer interning
Python pre-creates integers from -5 to 256 and reuses them forever. Beyond that, new objects are made each time.
a = 5; b = 5
print(a is b) # True β SAME object in memory!
a = 1000; b = 1000
print(a is b) # False β different objects
Small int (-5 to 256): interned Large int: new object each time
ββββββββββββ ββββββββββββ ββββββββββββββββ
β a = 5 ββββ β a = 1000 ββββββΊβ int: 1000 (A)β
ββββββββββββ ββββΊ ββββββββββββββββ ββββββββββββ ββββββββββββββββ
β β int: 5 β
ββββββββββββ β β shared foreverβ ββββββββββββ ββββββββββββββββ
β b = 5 ββββ ββββββββββββββββ β b = 1000 ββββββΊβ int: 1000 (B)β
ββββββββββββ ββββββββββββ ββββββββββββββββ
a is b β True a is b β False
id(a) == id(b) β id(a) != id(b) β
Diagram 5 β How memory gets freed: reference counting
x = [1,2,3] βββΊ y = x βββΊ del y βββΊ del x
ref count = 1 ref count = 2 ref count = 1 ref count = 0
β freed! ποΈ
When ref count hits 0, Python automatically frees that memory from the heap.
No manual malloc/free needed β unlike C/C++.
The full picture
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HEAP MEMORY β
β β
β type (metaclass) β
β β² β² β
β β β β
β int str β built-in classes β
β β² β
β β β
β ObjectCounter β your class β
β { num_instances: 2 } β
β β² β² β
β β β β
β instance_1 instance_2 β your objects β
β β
β 5, 256, "hello" β interned primitives β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STACK MEMORY β
β x, y βββββββββββββββββββββββββΊ (point to β
β heap objects) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
One question about a class counter led me to understand stack vs heap, reference semantics, interning, and garbage collection.
Python hides a lot of complexity behind clean syntax. But when you peek under the hood, it's elegant all the way down.
If this was useful, I write about things I'm actively learning as an SDE. Follow along. π
#Python #SoftwareEngineering #WebDevelopment #Programming #LearningInPublic #ComputerScience #100DaysOfCode
Top comments (0)