Advanced 3 min · March 17, 2026

Python __slots__ — Why Your Subclass Still Has __dict__

MemoryError processing 3.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
Quick Answer
  • __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

Sometimes __slots__ is the wrong tool. Here are alternatives
  • 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.

Class Without __slots__ vs With __slots__
FeatureWithout __slots__With __slots__
Memory per instance (3 attributes)~280 bytes~56 bytes
Attribute access speedHash lookup (~80ns)Direct offset (~65ns)
Dynamic attribute assignmentAllowedBlocked (AttributeError)
C-level __dict__ presenceAlways presentAbsent (unless '__dict__' included)
Weak reference supportBuilt-inRequires '__weakref__' in __slots__
Inheritance behaviorStandard inheritanceMust redefine in each subclass
Compatibility with frameworksWorks with allBreaks 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
    __slots__ is a class attribute that tells Python to store instance attributes in a fixed-size C-level array instead of a per-instance dictionary. Use it when you have many thousands of small objects and need to reduce memory usage (by about 50-70%). It also prevents accidental attribute creation and provides a modest speed improvement for attribute access. The trade-off is you lose dynamic attribute assignment and compatibility with frameworks that rely on __dict__.
  • QWhat happens if you try to add a new attribute to an object whose class uses __slots__?JuniorReveal
    Python raises AttributeError because there is no __dict__ to store the new attribute. The class definition explicitly forbids any attribute not listed in __slots__. This is the fundamental trade-off: you get performance and memory benefits, but you sacrifice flexibility.
  • QHow does __slots__ affect memory usage in Python?Mid-levelReveal
    Without __slots__, each instance carries a __dict__ overhead of about 232 bytes (for a small dict) plus the object header. With __slots__, the overhead is reduced to just the object header and the slot values — typically 40-80 bytes total. For 100,000 objects with 3 attributes, that's a saving of roughly 67% (56 MB vs 18 MB). The saving scales linearly with the number of instances.
  • QCan __slots__ be used with inheritance? What are the pitfalls?SeniorReveal
    Yes, but with care. Each subclass must define its own __slots__ for any additional attributes. The parent's slots are automatically available in the child, but if the child doesn't define its own __slots__, it will still have a __dict__ and lose the memory benefit. A more subtle pitfall: if any parent in the hierarchy does NOT use __slots__, then all children will retain __dict__ even if they define __slots__ — because the parent created __dict__ first. The only workaround is to include '__dict__' in the parent's __slots__ (but that defeats the memory purpose) or to refactor the hierarchy.
  • QWhat's the difference between __slots__ and @dataclass(slots=True)?Mid-levelReveal
    __slots__ is a low-level mechanism that tells CPython to omit __dict__. @dataclass(slots=True) (Python 3.10+) is a higher-level decorator that automatically generates __slots__ from the field definitions, plus adds __init__, __repr__, __eq__, etc. The end result is similar — no __dict__ and fixed attributes — but dataclasses with slots=True are less error-prone and more maintainable. Internally, dataclass(slots=True) still uses __slots__ underneath.

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

Previous
GIL — Global Interpreter Lock
11 / 17 · Advanced Python
Next
Abstract Base Classes in Python