In Part 1, we learned what slots is and why it saves memory. Now let's talk about why it can destroy your architecture — and the five traps that catch every developer who uses it without reading the fine print.
Trap 1: No Dynamic Attributes — Ever
class User:
__slots__ = ('name', 'age')
u = User("Abhishek", 40)
u.email = "*Emails are not allowed*" # AttributeError!
Without dict, there's nowhere to store email. This isn't a bug — it's the design. But it breaks workflows you rely on:
- Debugging: Can't attach temporary attributes for inspection
- Monkey-patching: Can't override behavior in tests
- Frameworks: Django ORM, SQLAlchemy (some modes), Pydantic v1 expect dict
The escape hatch (that isn't one):
class User:
__slots__ = ('name', 'age', '__dict__') # You're doing it wrong
Now you pay for both slots and dict. The worst of both worlds.
Trap 2: Multiple Inheritance Breaks Silently
class A:
__slots__ = ('a',)
class B:
__slots__ = ('b',)
class C(A, B): # TypeError: multiple bases have instance lay-out conflict
pass
Python can't merge two fixed C arrays into one. You'd need:
class C(A, B):
__slots__ = ('a', 'b', 'c') # Manual layout management
One wrong field order, and you're corrupting memory offsets. This is C-level debugging in Python.
Rule: Never use slots in base classes meant for public inheritance.
Trap 3: pickle and copy Lie to You
import pickle
class Config:
__slots__ = ('host', 'port')
c = Config()
c.host = 'localhost'
c.port = 8080
data = pickle.dumps(c)
c2 = pickle.loads(data) # Works... until it doesn't
pickle handles slots — mostly. But add weakref for compatibility, and the serialized format changes silently. Upgrade Python versions, and old pickles may fail with cryptic errors.
copy.deepcopy has similar edge cases. The object looks the same. The behavior isn't.
Trap 4: Frameworks Reject You
You don't discover this until integration testing. By then, slots is baked into your core models.
Trap 5: Memory Savings Are Context-Dependent
slots wins at scale. For most applications, the savings don't justify the constraints. Measure first. sys.getsizeof() and tracemalloc will tell you if you actually have a problem.
When slots Is the Right Call
Use it when all of these are true:
- You're creating millions of instances
- The attribute set is fixed and known upfront
- You control the full inheritance chain
- You don't need dynamic attributes, weak references, or arbitrary
framework integration
- You've profiled and confirmed dict is your actual bottleneck
That's a narrow window. Most Python code doesn't live there.
The Unified Lesson
slots isn't an optimization. It's a contract change. It trades flexibility for memory efficiency, and that trade is permanent for the class hierarchy.
The trap isn't technical — it's psychological. Developers see "memory optimization" and apply it everywhere. But Python's strength is flexibility. slots buys you C-struct memory layout at the cost of Python's dynamic nature.
Know the price before you pay it.