What is `__slots__`, and what problem does it solve?
Quick Answer
By default, every instance gets a __dict__ to store arbitrary attributes, which costs memory per instance. Declaring __slots__ = ("x", "y") tells Python to allocate fixed storage for exactly those attribute names instead of a per-instance dict, cutting memory use significantly for classes with many instances, at the cost of losing dynamic attribute assignment and needing extra care with multiple inheritance.
Detailed Answer
The default cost: a __dict__ per instance
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.__dict__ # {'x': 1, 'y': 2}
p.z = 3 # works -- __dict__ is dynamic, any attribute can be added
Every instance carries its own dict for attribute storage, which is
flexible but has real memory overhead — around 100+ bytes per instance
just for the dict, on top of the attributes themselves. This is
significant if you create millions of small objects.
__slots__: fixed, dict-free storage
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.z = 3 # AttributeError: 'Point' object has no attribute 'z'
p.__dict__ # AttributeError: no __dict__ at all
With __slots__, Python allocates fixed-size storage for exactly the
named attributes (implemented as descriptors under the hood), eliminating
the per-instance dict entirely. For a class with millions of instances
(points in a large dataset, nodes in a graph), this can cut memory
use by 40-50%+ and speed up attribute access slightly too.
The tradeoffs
- No dynamic attributes — assigning an attribute not in
__slots__raisesAttributeError. This is often a feature (catches typos early) but breaks code that relies on ad-hoc attribute assignment. - No default
__dict__or__weakref__unless explicitly included in__slots__. If you need weak references to instances, add"__weakref__"to the slots tuple. - Multiple inheritance is tricky — at most one base class in the MRO
can have non-empty
__slots__with actual layout conflicts. Combining multiple non-empty-__slots__bases raisesTypeError: multiple bases have instance lay-out conflict. - Every subclass needs its own
__slots__— if a subclass doesn't declare__slots__, it gets a__dict__anyway, silently undoing the memory savings for that subclass's instances.
When to use it
Reach for __slots__ on classes you'll instantiate in large numbers (data
records, tree/graph nodes, simple value objects) where memory footprint
matters, not as a default for every class, since it removes flexibility
for comparatively small classes where the memory savings don't matter.
__slots__ trades away per-instance dict
flexibility for fixed, lower-memory attribute storage. That's a meaningful
win when instantiating a class millions of times, but something to opt into
deliberately (and consistently across a whole inheritance chain), not a
default.