What is `__slots__`, and what problem does it solve?

6 minadvancedoopslotsperformancememory

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__ raises AttributeError. 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 raises TypeError: 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.

Related Resources