What does `__slots__` do in Python, and when should you use it?
`__slots__` replaces the per-instance `__dict__` with a fixed-size C array of slot descriptors, cutting memory usage per instance by 40–60% and speeding up attribute access. Use it for classes that create many small, fixed-attribute instances — but be aware it prevents dynamic attribute assignment and complicates multiple inheritance.
How to think about it
This one shows up at companies working at scale — streaming pipelines, graph algorithms, anything spawning millions of small objects. The interviewer wants to know you understand why an ordinary instance is expensive, and what __slots__ trades away to fix it.
Every normal Python instance carries a __dict__ — a per-instance hash map that stores its attributes, so you can add new ones at any time. That flexibility isn’t free: the dict costs roughly a hundred-plus bytes per instance even when nearly empty. Across millions of small objects — graph nodes, events, sensor readings — that overhead dominates your memory. __slots__ tells Python to drop the __dict__ and store the named attributes in a compact, fixed C-level layout instead.
A worked example
import sys
class RegularPoint:
def __init__(self, x, y):
self.x = x
self.y = y
class SlottedPoint:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
rp = RegularPoint(1.0, 2.0)
sp = SlottedPoint(1.0, 2.0)
rp_size = sys.getsizeof(rp) + sys.getsizeof(rp.__dict__) # instance + its dict
sp_size = sys.getsizeof(sp) # no __dict__ to add
per = rp_size - sp_size
print(f"RegularPoint: {rp_size} bytes (instance + __dict__)")
print(f"SlottedPoint: {sp_size} bytes")
print(f"Saving per instance: {per} bytes")
print(f"Saving at 1M instances: ~{per * 1_000_000 / 1e6:.0f} MB")
# Slots forbid attributes you didn't declare
try:
sp.z = 3.0
except AttributeError as e:
print("AttributeError:", e)
# A regular instance accepts new attributes freely
rp.z = 3.0
print("RegularPoint.z:", rp.z)
RegularPoint: 152 bytes (instance + __dict__)
SlottedPoint: 48 bytes
Saving per instance: 104 bytes
Saving at 1M instances: ~104 MB
AttributeError: 'SlottedPoint' object has no attribute 'z'
RegularPoint.z: 3.0
The slotted instance is roughly a third the size, and at a million instances that 104 bytes apiece adds up to about 104 MB saved. The cost sits right below it: sp.z = 3.0 raises, because slots fix the attribute set at class-definition time, while the regular instance happily takes a brand-new z.
Rules and caveats
- A subclass that doesn’t redeclare
__slots__quietly regains a__dict__— and the saving is gone. - To allow extra ad-hoc attributes alongside the slots, add
"__dict__"to__slots__explicitly. - Weak references break unless you include
"__weakref__"in__slots__. - On Python 3.10+,
@dataclass(slots=True)gives you slots without writing them by hand — the cleaner choice for new code:
from dataclasses import dataclass
@dataclass(slots=True)
class Point:
x: float
y: float
When to use it
Reach for __slots__ when you’re creating tens of thousands or more instances of a class with a fixed, known set of attributes — graph nodes, rows in an in-memory store, events in a stream. Below that scale, the memory win isn’t worth the lost flexibility.