When Python applications scale, memory often becomes the hidden bottleneck. A service that performs flawlessly with thousands of objects can struggle when handling millions. One underused but powerful feature that directly addresses this is __slots__.
This article explains what __slots__ is, why it matters, when to use it, and how to apply it in production-quality code.
The Problem: Why Normal Python Objects Are Heavy
By default, every Python object stores its attributes in a dynamic dictionary (__dict__). This provides flexibility but costs memory and adds lookup overhead.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
Each instance of Point internally contains:
- A PyObject header
- A pointer to
__dict__ - A separate dictionary object
- Hash table entries for attributes
For a handful of objects, this overhead is negligible. For millions, it becomes expensive.
What Is __slots__?
__slots__ tells Python to allocate a fixed layout for attributes instead of using a dictionary.
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
Now each instance stores values in a compact structure rather than a hash table.
Key effects:
- Removes
__dict__from instances - Reduces memory usage significantly
- Speeds up attribute access slightly
- Prevents creation of undeclared attributes
Demonstrating the Difference (Colab-Ready)
Below is a practical benchmark you can run in Google Colab.
import tracemalloc
import gc
from dataclasses import dataclass
N = 1_000_000
class NormalPoint:
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
@dataclass(slots=True)
class DataClassPoint:
x: int
y: int
def measure_memory(cls):
gc.collect()
tracemalloc.start()
objs = [cls(i, i) for i in range(N)]
current, _ = tracemalloc.get_traced_memory()
tracemalloc.stop()
del objs
gc.collect()
return current / (1024 * 1024)
print("Creating objects...\n")
print("Normal class:", measure_memory(NormalPoint), "MB")
print("Slotted class:", measure_memory(SlottedPoint), "MB")
print("Dataclass(slots=True):", measure_memory(DataClassPoint), "MB")
Typical outcome:
- Normal class: Highest memory usage
- Slotted class: 40–60% less memory
- Dataclass with slots: Similar savings with cleaner syntax
Why Memory Drops So Much
Normal Instance Layout
Object header
Pointer to __dict__
Dictionary object
Hash table entries
Slotted Instance Layout
Object header
Fixed array of attributes
No dictionary, no hash table — just direct storage.
Preventing Accidental Attributes
Slotted classes reject unknown attributes, reducing bugs caused by typos.
p = SlottedPoint(1, 2)
p.z = 5 # Raises AttributeError
This can act as a lightweight structural guarantee in large systems.
Modern Best Practice: Dataclasses with Slots
Python 3.10 introduced built-in support for slots in dataclasses.
from dataclasses import dataclass
@dataclass(slots=True)
class Order:
id: int
symbol: str
quantity: int
price: float
def value(self) -> float:
return self.quantity * self.price
Why this is ideal for production:
- Concise syntax
- Automatic method generation
- Type hints
- Memory efficiency
- Better maintainability
Real-World Use Cases
__slots__ shines when you create many objects with identical structure:
- Market data and trading systems
- Network packets and telemetry
- Game engines
- Large graph or geometry models
- Simulation systems
- Abstract syntax trees
- Embedded or memory-constrained environments
Inheritance Considerations
Slots interact with inheritance in non-obvious ways.
class A:
__slots__ = ("a",)
class B(A):
__slots__ = ("b",)
If a subclass omits __slots__, it regains a __dict__, defeating the memory savings.
Limitations and Trade-offs
Use __slots__ carefully. It reduces flexibility.
Avoid it when:
- You need dynamic attributes
- You rely on frameworks expecting
__dict__ - You use complex multiple inheritance
- You frequently modify class structure
- You depend on generic serialization tools
Additionally, slotted classes cannot be weak-referenced unless you include "__weakref__" in the slot list.
When Should You Use It?
Use __slots__ when all of the following are true:
- You create large numbers of objects
- The attribute set is fixed
- Memory efficiency matters
- Performance is important
- You control the class design
Otherwise, the default behavior is usually more practical.
Key Takeaway
__slots__ is not a beginner feature, but it is a powerful optimization tool for serious Python systems.
In one sentence:
__slots__trades flexibility for memory efficiency, performance, and structural discipline.
For modern Python, the recommended approach is:
Use @dataclass(slots=True) for most production scenarios.
