"Python __slots__ Memory Optimization: When It Actually Pays"
"When does `__slots__` actually save memory? A measured, no-hype guide to python __slots__ memory optimization — with benchmarks, dataclass traps, and the fixes for weakref, subclasses, and pickle."
Your parser turns 200,000 CSV rows into objects and your container's memory graph climbs like it's late for a meeting. You read that one magic line — __slots__ — cuts memory dramatically, so you sprinkle it on every class. Half your tests break, your weakref cache throws TypeError , and the memory win is nowhere to be found. This guide to python __slots__ memory optimization skips the hype: you'll measure the real payoff, learn exactly when slots earn their keep, and dodge the four traps that bite everyone. What you'll learn What __slots__ changes in CPython's instance layout, and why it saves memory How to measure the payoff with a real benchmark (not vibes) When to use __slots__ — and the three conditions that must all hold How slots=True dataclasses behave, including the missing- __dict__ surprise Fixes for weakref, subclass, multiple-inheritance, and pickle gotchas A decision table you can apply to any class in your codebase __dict__ vs __slots__ : where your memory actually goes Every normal Python object carries a __dict__ — a full hash table allocated per instance — to hold its attributes. Even for an object with three attributes, that dict is hundreds of bytes of overhead. Declaring __slots__ tells CPython to skip the dict and store attributes in fixed C-level slots instead: import sys class Record: def __init__(self, symbol, price, qty): self.symbol = symbol self.price = price self.qty = qty class SlottedRecord: __slots__ = ("symbol", "price", "qty") def __init__(self, symbol, price, qty): self.symbol = symbol self.price = price self.qty = qty plain = Record("AAPL", 231.5, 100) slotted = SlottedRecord("AAPL", 231.5, 100) print(sys.getsizeof(plain), "+", sys.getsizeof(plain.__dict__)) # 48 + 296 -> ~344 bytes total per instance…