__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
✦ Definition~90s read
What is Python Slots?
__slots__ is a class-level declaration that tells Python to reserve a fixed-size array for instance attributes instead of the default per-instance __dict__. This eliminates the hash table overhead that every Python object normally carries — roughly 40–60 bytes per instance for the dict itself, plus per-entry overhead.
★
Think of a Python object like a backpack.
For millions of objects, that difference is the difference between fitting in RAM and swapping to disk. But __slots__ is not a general-purpose optimization; it's a contract that says "these attributes, and only these attributes, exist on every instance." The moment you inherit from a slotted class, that contract breaks unless you explicitly re-declare __slots__ in the subclass — and if any ancestor in the MRO lacks __slots__, your subclass silently gets a __dict__ anyway, negating the memory win.
Tools like SQLAlchemy's declarative base and attrs/cattrs use __slots__ aggressively in their generated classes, but only when they control the entire hierarchy. For ad-hoc subclassing, you're better off measuring first: the access speed improvement (roughly 15–30% faster attribute reads) rarely matters outside hot loops, and the inability to dynamically add attributes at runtime can break serialization, mocking, and debugging tools like pdb or inspect.
Use __slots__ when you have millions of homogeneous objects and you've proven the dict overhead is your bottleneck — not because it feels faster.
Plain-English First
Think of a Python object like a backpack. Normally, each backpack has a separate duffel bag (__dict__) where you can toss any item whenever you want. __slots__ replaces that duffel bag with fixed compartments sewn into the backpack. You can only put the items you planned for, but the backpack is lighter and you find things faster because you know exactly where they are.
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.
What __slots__ Actually Does — and Doesn't
__slots__ is a class-level attribute that tells Python to reserve a fixed-size array for instance attributes instead of the per-instance __dict__. This eliminates the hash table overhead — each slot becomes a descriptor that stores the value at a known offset in the instance's internal struct. The result: each instance saves ~64 bytes (the __dict__ overhead) and attribute access becomes a direct array lookup instead of a hash-table probe, giving a measurable speedup in tight loops.
Crucially, __slots__ only applies to the class that defines it. Subclasses that don't redeclare __slots__ will still get a __dict__ — and if they do declare __slots__, they inherit the parent's slots but also get their own __dict__ unless they explicitly set __slots__ = () to suppress it. This is the single most common source of confusion: developers assume __slots__ is inherited like a method, but it's not — it's a per-class declaration that controls the layout of that class's instances.
Use __slots__ when you have many instances (thousands or more) of a simple data-holder class — for example, ORM models, game entities, or configuration objects. The memory savings are linear: 100,000 instances save ~6 MB of dict overhead. But never use __slots__ on classes that need dynamic attribute assignment, weak references (unless you add __weakref__ to slots), or inheritance chains where subclasses add attributes — the complexity quickly outweighs the benefit.
Slots Are Not Inherited
A subclass without __slots__ always gets a __dict__, even if the parent uses __slots__. To suppress it, the subclass must explicitly set __slots__ = ().
Production Insight
A team added __slots__ to a base 'Event' class to reduce memory in a high-throughput analytics pipeline. Months later, a new subclass added a 'tags' attribute — and silently started using __dict__, negating the memory win and causing a 3x memory spike under load. The rule: if you use __slots__, every subclass must also declare __slots__ or you lose the benefit.
Key Takeaway
__slots__ saves memory by removing __dict__, but it's a per-class declaration, not inherited.
Subclasses without __slots__ get a __dict__ — always check your inheritance chain.
Only use __slots__ for high-volume data containers; avoid it for dynamic or heavily subclassed classes.
thecodeforge.io
Python __slots__: Inheritance & __dict__ Trap
Python Slots
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).
ExamplePYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import sys
# Without __slots__ — each instance has a __dict__classPointDict:
def__init__(self, x, y):
self.x = x
self.y = y
# With __slots__ — no __dict__, fixed attributes onlyclassPointSlots:
__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) = ~280print(sys.getsizeof(p_slots)) # ~56 bytes — no dict# __dict__ exists on PointDict but not PointSlotsprint(hasattr(p_dict, '__dict__')) # Trueprint(hasattr(p_slots, '__dict__')) # False# Dynamic attribute assignment blockedtry:
p_slots.z = 3.0# AttributeErrorexceptAttributeErroras e:
print(e) # 'PointSlots' object has no attribute 'z'
Output
280
56
True
False
'PointSlots' object has no attribute 'z'
Think of __slots__ as a struct
A Python class without __slots__ is like an open dictionary in memory.
__slots__ freezes the attribute names at class definition time.
Access becomes a C pointer offset instead of a hash table probe.
You trade flexibility for speed and memory — exactly like choosing a struct over a dict in C.
Production Insight
Removing __dict__ saves memory but breaks every framework that relies on attribute injection (e.g., Django model fields, SQLAlchemy ORM).
Test all libraries that touch instances before rolling out __slots__.
Rule: profile first; apply __slots__ only to leaf classes with high instance counts.
Key Takeaway
__slots__ = ('x', 'y') removes the per-instance dict and blocks new attributes.
Memory drops by ~70% for small objects.
The trade-off: no flexibility, no __dict__-based introspection.
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__:
ExamplePYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import tracemalloc
classEventDict:
def__init__(self, ts, kind, value):
self.ts = ts; self.kind = kind; self.value = value
classEventSlots:
__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 inrange(N)]
_, peak_dict = tracemalloc.get_traced_memory()
tracemalloc.stop()
tracemalloc.start()
events_slots = [EventSlots(i, 'click', i * 1.5) for i inrange(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}%")
Output
Dict: 56.2 MB
Slots: 18.4 MB
Saving: 67%
tracemalloc measures peak not total
tracemalloc shows peak memory usage, which can include temporary overhead. For precise per-object memory, use sys.getsizeof() on a single instance and multiply by N.
Production Insight
At 10 million objects, the dict version consumes ~5.6 GB vs ~1.8 GB with __slots__.
That extra 3.8 GB often triggers swap or OOM in containerized environments.
Rule: when your object count exceeds 500k, __slots__ is not optional — it's preventive maintenance.
Scale linearly: at 10M objects, that's 3.8 GB saved.
If you're not measuring per-instance memory, you're guessing.
When to Use __slots__ for Memory
IfObject count < 10k
→
UseNot worth the trade-off. Use a regular class or a namedtuple.
If10k to 500k objects
→
UseConsider it if you're memory-constrained (e.g., embedded Python, microservices with tight limits).
If500k+ objects or heavy GC pressure
→
UseUse __slots__. The memory and GC benefits outweigh the loss of flexibility.
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.
slots_inheritance.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
classBase:
__slots__ = ('x', 'y')
classChildWithSlots(Base):
__slots__ = ('z',) # <- only new slots, parent's x,y merged automaticallyclassChildWithoutSlots(Base):
pass # <- BEWARE: this child still has __dict__!
c1 = ChildWithSlots()
print(hasattr(c1, '__dict__')) # False
c2 = ChildWithoutSlots()
print(hasattr(c2, '__dict__')) # True! Memory benefit lost.# What if parent has no __slots__?classNoSlotsBase:
passclassChildSlotsOnNoSlots(NoSlotsBase):
__slots__ = ('a',)
c3 = ChildSlotsOnNoSlots()
print(hasattr(c3, '__dict__')) # True — inherited from NoSlotsBaseprint(hasattr(c3, '__slots__')) # True — but __dict__ still exists
Production Insight
When a parent class lacks __slots__, every child — even one that defines __slots__ — retains __dict__.
This is the #1 cause of 'I added __slots__ but memory didn't drop'.
Fix: audit the entire class hierarchy; if any ancestor omits __slots__ you won't get the full benefit.
Key Takeaway
Each class in the hierarchy must define its own __slots__.
If any parent lacks __slots__, all children keep __dict__.
Rule: either go full slots (all classes) or not at all.
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.
The 15-20% speedup shown here is under ideal conditions — single-threaded, no I/O, no dynamic attribute lookups. In a real application the overall improvement is often 1-3% because attribute access is rarely the bottleneck. Profile first.
Production Insight
The speed gain is real but small. If your application spends 90% of CPU time in attribute access loops (e.g., vector math in video processing), __slots__ helps. Otherwise, the memory savings are the real win.
Rule: measure attribute access as a percentage of total CPU before chasing this micro-optimization.
Key Takeaway
Attribute reads: 10-20% faster with __slots__.
Attribute writes: similar gain.
But total app speedup < 3% unless attribute access dominates.
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__.
If any part of your system uses weak references (e.g., caching, observers), you must include '__weakref__' in __slots__. Otherwise you'll get a runtime TypeError.
Production Insight
Common pattern: teams add __slots__ to a critical DTO class and later discover that a monitoring library or a caching layer uses weak references. The fix is trivial but the outage is real.
Rule: always include '__weakref__' in __slots__ unless you've verified no weak references are used.
Key Takeaway
Use __slots__ for high-volume, fixed-shape value objects.
Avoid if you need dynamic attributes or ORM integration.
Include '__weakref__' proactively to prevent subtle breakage.
Decision: Should I use __slots__?
IfNeed dynamic attribute assignment or object has many optional fields
→
UseAvoid __slots__. Use a regular class or a dict-based approach.
IfUsing Django, SQLAlchemy ORM, or similar framework that relies on __dict__
→
UseAvoid __slots__. These frameworks need __dict__ for dynamic property injection.
IfHigh instance count (>500k), fixed attributes, no framework restriction
→
UseUse __slots__. Include '__weakref__' if needed.
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.
dataclass_slots.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from dataclasses import dataclass
# Python 3.10+ — slots=True generates __slots__ automatically
@dataclass(slots=True)
classPoint:
x: float
y: float
p = Point(1.0, 2.0)
print(hasattr(p, '__dict__')) # Falseprint(hasattr(p, '__slots__')) # Trueprint(p.x) # 1.0# But you still can't add attributes dynamicallytry:
p.z = 3.0exceptAttributeErroras e:
print(e)
# For pre-3.10, you can't use slots=True; fallback to manual __slots__
Use @dataclass(slots=True) when possible
If you're on Python 3.10+, let the dataclass decorator handle __slots__ generation. It's less error-prone and keeps your class definition clean.
Production Insight
Many teams overuse __slots__ because they've heard it's 'faster'. In practice, proper data structures (like list of tuples vs list of objects) often yield bigger wins.
Rule: use __slots__ only when profiling confirms that dict overhead is a measurable fraction of memory or GC time.
Key Takeaway
Prefer @dataclass(slots=True) for new code (3.10+).
Measure first; the memory win is real, but the code cost is real too.
Never apply __slots__ to a base class used by framework-managed subclasses.
Why __slots__ Breaks Pickling (and What to Do)
You just decorated a high-volume data class with __slots__ to cut memory. Great. Now your serialization pipeline throws AttributeError: 'MyRecord' object has no attribute '__dict__'. This is the first thing that burns juniors in production.
Pickle and copy.deepcopy rely on __dict__ by default. When you kill the dict, you kill naive serialization. The fix isn't to remove slots — it's to implement __getstate__ and __setstate__. Or switch to __reduce__ if you need pickle protocol 2+.
For JSON serialization, you already have a method. Add __json__ or use dataclasses with slots=True which handles this for you. Never assume your downstream consumers can handle slot-based objects without explicit support.
If you use multiprocessing, check your pickling path before rollout. I've seen a 200-node cluster silently fail because a slot-based config object couldn't be serialized across workers. Test this during system testing, not after deployment.
slots_pickle_fix.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// io.thecodeforge
import pickle
classRecord:
__slots__ = ('id', 'value')
def__init__(self, id, value):
self.id = id
self.value = value
# Required for pickle to work without __dict__def__getstate__(self):
return {slot: getattr(self, slot) for slot inself.__slots__}
def__setstate__(self, state):
for slot, val in state.items():
setattr(self, slot, val)
r = Record(42, "production")
data = pickle.dumps(r)
restored = pickle.loads(data)
print(restored.id, restored.value) # 42 production
Output
42 production
Production Trap:
Celery, Redis queues, and Spark all use pickle. If your object has __slots__, you MUST implement __getstate__/__setstate__ before sending it to a worker. Silent failures cost hours of debugging.
Key Takeaway
Always pair __slots__ with __getstate__ and __setstate__ if your object crosses process boundaries via pickle.
How __slots__ Breaks Weak References (Fixed With __weakref__)
Your caching layer uses weakref.WeakValueDictionary to hold slot objects. Good idea — until objects get garbage collected immediately because you forgot one line. When you define __slots__, you also remove the __weakref__ attribute that Python's garbage collector uses for weak references.
The symptom: Every object you add to the weak dictionary disappears instantly. Your cache evicts entries on insertion. This is another silent failure that only shows up under load.
Fix: Add '__weakref__' to your __slots__ tuple. It's a reserved slot name that restores weak reference support without bringing back the full __dict__. Do this whenever your objects participate in any caching, observer pattern, or event system.
If you're using weakref.finalize for cleanup, same problem applies. Python 3.4+ has __weakref__ as a standard slot, but you must declare it explicitly when overriding __slots__. Otherwise your destructors never run.
Rule of thumb: If your class is used in a cache, callbacks, or async context, add '__weakref__' to slots. It costs ~8 bytes per object and saves hours of hair-pulling.
slots_weakref.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge
import weakref
classCacheNode:
# Without __weakref__, this object can't be weakly referenced
__slots__ = ('key', 'data', '__weakref__')
def__init__(self, key, data):
self.key = key
self.data = data
cache = weakref.WeakValueDictionary()
node = CacheNode(1, "payload")
cache[node.key] = node
print(len(cache)) # 1, not 0# Without __weakref__, this would print 0 — the object dies immediately
Output
1
Saves Your Cache:
Every slot-based class used in caching, event emitters, or async workers must declare '__weakref__' in __slots__. Without it, weak references silently break.
Key Takeaway
Add '__weakref__' to your __slots__ tuple whenever your objects are stored in weak caches or used with callbacks.
● Production incidentPOST-MORTEMseverity: high
The 4-Million-Point Memory Blowup
Symptom
Process terminated with MemoryError after processing ~3.2 million points. The dev team expected ~220 MB but the process hit 1.2 GB.
Assumption
The team assumed that defining __slots__ in the base class automatically applied to all subclasses.
Root cause
The base class GeoPoint defined __slots__ = ('lat', 'lon'), but a subclass GeoPointWithAltitude did not define its own __slots__. Instances of the subclass still had a __dict__ because __slots__ is not inherited.
Fix
Add __slots__ = ('altitude',) to the subclass. Or add '__dict__' to the base class __slots__ if you need flexibility. The fix reduced memory from 280 bytes per instance to 64 bytes.
Key lesson
__slots__ is not inherited. Every subclass must define its own __slots__ to avoid the __dict__ penalty.
Always verify memory consumption with sys.getsizeof() on instances of every subclass in the hierarchy.
If you need both slots and dynamic attributes, include '__dict__' in __slots__ — but you lose the memory benefit.
Production debug guideFind out why your quick memory optimization isn't working as expected.3 entries
Symptom · 01
Instance memory doesn't drop after adding __slots__
→
Fix
Check if the class or any parent class has __dict__ present. Use sys.getsizeof() on an instance and look for __dict__ attribute. If present, verify inheritance chain.
Symptom · 02
AttributeError when setting a new property at runtime
→
Fix
You hit the exact trade-off: __slots__ blocks dynamic attributes. Either add the attribute to __slots__ or remove __slots__ entirely. For production, log the action and decide based on requirements.
Symptom · 03
Subclass instances still have large memory footprint
→
Fix
Subclass must define its own __slots__. Use help(Subclass) to see if __slots__ appears. If missing, add it with only the new attributes.
★ Quick __slots__ Debugging CommandsCopy-paste these to diagnose __slots__ problems fast.
Need to check if a class uses __slots__−
Immediate action
Check for __dict__ and __slots__ attributes
Commands
print(hasattr(instance, '__dict__'))
print(getattr(cls, '__slots__', 'No __slots__'))
Fix now
If __dict__ exists despite __slots__, remove the __dict__ inheritance by adding '__slots__' in every class.
Memory usage higher than expected with __slots__+
Immediate action
Measure a single instance with sys.getsizeof()
Commands
import sys; print(sys.getsizeof(instance))
If __dict__ present, recalc raw = sys.getsizeof(instance) - sys.getsizeof({})
Fix now
If __dict__ still there, audit class hierarchy. Add __slots__ to every subclass.
Class Without __slots__ vs With __slots__
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
1
__slots__ replaces the per-instance __dict__ with fixed C-level descriptors
significant memory savings for many small objects.
2
With __slots__, you cannot add new attributes not declared in __slots__ at runtime.
3
Subclasses do not inherit __slots__ restrictions unless they also define __slots__.
4
If a class with __slots__ inherits from a class without __slots__, the __dict__ is still present.
5
Do not use __slots__ prematurely
only apply it when you have profiled memory usage and confirmed a problem.
6
Include '__weakref__' in __slots__ if your code uses weak references.
7
Prefer @dataclass(slots=True) for Python 3.10+
same benefit, less boilerplate.
Common mistakes to avoid
4 patterns
×
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 PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is the purpose of __slots__ in Python and when would you use it?
Q02JUNIOR
What happens if you try to add a new attribute to an object whose class ...
Q03SENIOR
How does __slots__ affect memory usage in Python?
Q04SENIOR
Can __slots__ be used with inheritance? What are the pitfalls?
Q05SENIOR
What's the difference between __slots__ and @dataclass(slots=True)?
Q01 of 05JUNIOR
What is the purpose of __slots__ in Python and when would you use it?
ANSWER
__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__.
Q02 of 05JUNIOR
What happens if you try to add a new attribute to an object whose class uses __slots__?
ANSWER
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.
Q03 of 05SENIOR
How does __slots__ affect memory usage in Python?
ANSWER
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.
Q04 of 05SENIOR
Can __slots__ be used with inheritance? What are the pitfalls?
ANSWER
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.
Q05 of 05SENIOR
What's the difference between __slots__ and @dataclass(slots=True)?
ANSWER
__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.
01
What is the purpose of __slots__ in Python and when would you use it?
JUNIOR
02
What happens if you try to add a new attribute to an object whose class uses __slots__?
JUNIOR
03
How does __slots__ affect memory usage in Python?
SENIOR
04
Can __slots__ be used with inheritance? What are the pitfalls?
SENIOR
05
What's the difference between __slots__ and @dataclass(slots=True)?
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
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.
Was this helpful?
02
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__.
Was this helpful?
03
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.
Was this helpful?
04
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.
Was this helpful?
05
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.