Python __slots__ — Why Your Subclass Still Has __dict__
MemoryError processing 3.
- __slots__ replaces instance __dict__ with fixed C-level slot descriptors
- Memory savings: ~67% less per instance for 3-attribute objects (56 MB vs 18 MB for 100k instances)
- Attribute access uses direct offset instead of hash lookup — ~15% faster reads in CPython
- You lose dynamic attribute assignment: trying to set an undeclared attribute raises AttributeError
- Biggest mistake: expecting __slots__ to work across inheritance without defining it in every child class
Most Python objects carry a __dict__ — a hash map storing all instance attributes. For a small number of large objects this is fine. For millions of small objects (coordinate points, events, records), the dict overhead becomes significant.
__slots__ is the mechanism for trading flexibility for efficiency. Once you define __slots__, your class no longer has a __dict__ per instance, and attributes are stored as fixed C-level offsets instead.
Basic __slots__ Usage
To use __slots__, declare a class-level attribute __slots__ containing a tuple or list of attribute names. That's it. CPython then allocates fixed-size descriptors for these names instead of a per-instance __dict__.
You can still assign values normally in __init__. The difference is you can't add new attributes after __init__. Trying to do so raises AttributeError.
This is the simplest way to get the memory win — but watch out for inheritance gotchas (see later section).
Memory Savings at Scale
The memory win is real when you handle tens of thousands of objects. Each Python object without __slots__ carries a __dict__ overhead of about 232 bytes (for a typical dict) plus the object header. With __slots__, you only have the object header and the slot values — typically 40-80 bytes total.
Here's a benchmark comparing 100,000 event objects with and without __slots__:
Inheritance and __slots__
Here's the trap most engineers hit: __slots__ in a parent class does NOT carry over to child classes. Each subclass must define its own __slots__, otherwise the subclass instances will still have a __dict__ — and you lose the memory benefit.
If a subclass defines __slots__, it can only include the new attributes it adds, not the parent's. Python merges them at the C level automatically.
What happens if a parent class does NOT use __slots__? Then any subclass that uses __slots__ will STILL have a __dict__ because the parent provides one. The only way to avoid that is to include '__dict__' in the parent's __slots__ (defeating the purpose) or to refactor the hierarchy.
Performance: Attribute Access Speed
Removing the dict hash lookup gives you a small but measurable speed boost for reading and writing attributes. In microbenchmarks, __slots__ attribute access is about 10-20% faster than dict-backed access. For most applications the difference is negligible, but in tight loops (e.g., game physics, data processing pipelines) it can add up.
Note that the speed gain comes from avoiding the hash computation and dict resize overhead, not from eliminating the attribute itself. Writing to a slot is still a Python attribute set operation, but it bypasses the dict insertion path.
Use Cases and Trade-offs
__slots__ shines where you have many small, simple objects. Classic use cases: - Data transfer objects (DTOs) representing rows, API responses, or log entries - Game entities (player positions, bullets, particles) - Large collections of immutable value objects (coordinates, timestamps) - Objects that are serialized/deserialized frequently (less memory pressure reduces GC pauses)
Trade-offs you must accept: 1. No dynamic attributes — every attribute must be declared at class definition. 2. Breaks some libraries: Django models, SQLAlchemy's ORM, and many patches that rely on __dict__. You can't use __slots__ with those out of the box. 3. Inheritance complexity as discussed. 4. Weak references: classes with __slots__ can't be weakly referenced unless you add '__weakref__' to __slots__. 5. Default values: You can't set default values in __slots__ directly; you need to handle them in __init__.
Alternatives and Best Practices
- namedtuple / SimpleNamespace: for immutable, lightweight objects without __slots__ hassle
- dataclass(slots=True) (Python 3.10+): automatic __slots__ generation with less boilerplate
- Manual dict usage: if you need many attributes but can use a single dict field
- __dict__ with __slots__: include '__dict__' in __slots__ to allow dynamic attributes while still getting some memory benefit (but you lose most of the savings)
Best practices: 1. Measure before and after: never rely on intuition. Use sys.getsizeof() and tracemalloc. 2. Keep __slots__ at the leaf classes of your hierarchy; avoid putting it on abstract base classes. 3. Document the trade-off explicitly in the class docstring. 4. If you inherit from a C extension type (e.g., tuple, list), __slots__ may not work; check the type's tp_dictoffset.
| Feature | Without __slots__ | With __slots__ |
|---|---|---|
| Memory per instance (3 attributes) | ~280 bytes | ~56 bytes |
| Attribute access speed | Hash lookup (~80ns) | Direct offset (~65ns) |
| Dynamic attribute assignment | Allowed | Blocked (AttributeError) |
| C-level __dict__ presence | Always present | Absent (unless '__dict__' included) |
| Weak reference support | Built-in | Requires '__weakref__' in __slots__ |
| Inheritance behavior | Standard inheritance | Must redefine in each subclass |
| Compatibility with frameworks | Works with all | Breaks ORMs, Django, etc. |
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.
- Include '__weakref__' in __slots__ if your code uses weak references.
- Prefer @dataclass(slots=True) for Python 3.10+ — same benefit, less boilerplate.
Common Mistakes to Avoid
- Forgetting to add '__weakref__' to __slots__
Symptom: TypeError: cannot create weak reference when any part of the code tries to use weakref.ref() on the object.
Fix: Add '__weakref__' to the __slots__ tuple: __slots__ = ('x', 'y', '__weakref__'). - Expecting __slots__ to propagate to subclasses
Symptom: Subclass instances still have __dict__ and memory savings are lost.
Fix: Define __slots__ in every subclass of the hierarchy. Only include new attributes. - Using __slots__ with a parent that lacks __slots__
Symptom: Child still has __dict__ because the parent provides it. Memory savings are nullified.
Fix: Either make all ancestors use __slots__ or accept that __dict__ persists. Consider refactoring to avoid mixed hierarchies. - Assuming __slots__ makes attribute access as fast as C structs
Symptom: Performance gain is modest (10-20%), not orders of magnitude. Expectation mismatch.
Fix: Profile attribute access relative to total CPU. Use __slots__ primarily for memory, not speed.
Interview Questions on This Topic
- QWhat is the purpose of __slots__ in Python and when would you use it?JuniorReveal
- QWhat happens if you try to add a new attribute to an object whose class uses __slots__?JuniorReveal
- QHow does __slots__ affect memory usage in Python?Mid-levelReveal
- QCan __slots__ be used with inheritance? What are the pitfalls?SeniorReveal
- QWhat's the difference between __slots__ and @dataclass(slots=True)?Mid-levelReveal
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__.
Does __slots__ speed up attribute access?
Yes, by about 10-20% in microbenchmarks. But in most applications the overall improvement is under 3% because attribute access is rarely the bottleneck. The memory savings are the primary benefit.
Can I have both __slots__ and dynamic attribute assignment?
Yes, by including '__dict__' in the __slots__ tuple: __slots__ = ('x', '__dict__'). This keeps __dict__ but adds the C-level slot descriptors for named attributes. However, you lose most of the memory benefit (about 80% of the savings) because the dict still exists.
Why can't I use __slots__ with Django models?
Django's ORM relies on __dict__ to inject dynamic attributes (like related objects, field tracking, and instance states). If a model class uses __slots__, Django can't set these attributes, causing AttributeError. You can include '__dict__' in __slots__ but then you lose the memory benefit.
That's Advanced Python. Mark it forged?
3 min read · try the examples if you haven't