Python __slots__ — What They Are and When to Use Them
- __slots__ replaces the per-instance __dict__ with fixed C-level descriptors — significant memory savings for many small objects.
- With __slots__, you cannot add new attributes not declared in __slots__ at runtime.
- Subclasses do not inherit __slots__ restrictions unless they also define __slots__.
__slots__ replaces the instance __dict__ with a fixed set of slot descriptors. This reduces memory per instance (no dict overhead), slightly speeds up attribute access (direct offset vs hash lookup), and prevents adding new attributes at runtime. The trade-off: you lose dynamic attribute assignment and __dict__-based introspection.
Basic __slots__ Usage
import sys # Without __slots__ — each instance has a __dict__ class PointDict: def __init__(self, x, y): self.x = x self.y = y # With __slots__ — no __dict__, fixed attributes only class PointSlots: __slots__ = ('x', 'y') def __init__(self, x, y): self.x = x self.y = y p_dict = PointDict(1.0, 2.0) p_slots = PointSlots(1.0, 2.0) print(sys.getsizeof(p_dict)) # ~48 bytes (object) + ~232 bytes (dict) = ~280 print(sys.getsizeof(p_slots)) # ~56 bytes — no dict # __dict__ exists on PointDict but not PointSlots print(hasattr(p_dict, '__dict__')) # True print(hasattr(p_slots, '__dict__')) # False # Dynamic attribute assignment blocked try: p_slots.z = 3.0 # AttributeError except AttributeError as e: print(e) # 'PointSlots' object has no attribute 'z'
56
True
False
'PointSlots' object has no attribute 'z'
Memory Savings at Scale
import tracemalloc class EventDict: def __init__(self, ts, kind, value): self.ts = ts; self.kind = kind; self.value = value class EventSlots: __slots__ = ('ts', 'kind', 'value') def __init__(self, ts, kind, value): self.ts = ts; self.kind = kind; self.value = value N = 100_000 tracemalloc.start() events_dict = [EventDict(i, 'click', i * 1.5) for i in range(N)] _, peak_dict = tracemalloc.get_traced_memory() tracemalloc.stop() tracemalloc.start() events_slots = [EventSlots(i, 'click', i * 1.5) for i in range(N)] _, peak_slots = tracemalloc.get_traced_memory() tracemalloc.stop() print(f"Dict: {peak_dict / 1024 / 1024:.1f} MB") print(f"Slots: {peak_slots / 1024 / 1024:.1f} MB") print(f"Saving: {(1 - peak_slots/peak_dict)*100:.0f}%")
Slots: 18.4 MB
Saving: 67%
🎯 Key Takeaways
- __slots__ replaces the per-instance __dict__ with fixed C-level descriptors — significant memory savings for many small objects.
- With __slots__, you cannot add new attributes not declared in __slots__ at runtime.
- Subclasses do not inherit __slots__ restrictions unless they also define __slots__.
- If a class with __slots__ inherits from a class without __slots__, the __dict__ is still present.
- Do not use __slots__ prematurely — only apply it when you have profiled memory usage and confirmed a problem.
Interview Questions on This Topic
- QWhat is the purpose of __slots__ in Python and when would you use it?
- QWhat happens if you try to add a new attribute to an object whose class uses __slots__?
- QHow does __slots__ affect memory usage in Python?
Frequently Asked Questions
Does __slots__ work with inheritance?
Only if all classes in the hierarchy define __slots__. If the parent class does not define __slots__ (or defines it without including __dict__), instances will still have a __dict__. Each class in the hierarchy should define only the new slots it introduces, not repeat the parent's slots.
Can I use __slots__ with dataclasses?
Yes. Python 3.10+ added slots=True to @dataclass: @dataclass(slots=True). This automatically generates __slots__ from the field definitions, giving you the memory benefits without manually writing __slots__.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.