What __slots__ Optimizes
__slots__ is useful when a class has a fixed set of instance attributes and you create many similar objects. A normal Python instance usually stores attributes in a per-instance __dict__. That dictionary is flexible, but it costs memory and lets every instance grow a different shape.
A slotted class declares the instance names up front. Python reserves storage for those names and installs descriptors on the class to read and write them. Unless you explicitly include __dict__, new attribute names are rejected.
Instance Storage: __dict__ vs Slots
With a regular instance, point.x = 1 means "store the key x in this object's attribute dictionary." With a slotted instance, point.x = 1 goes through the slot descriptor that knows where the x field lives for that instance.
That is the whole optimization: replace a flexible mapping with a fixed layout. The memory savings can be significant when the instance count is high, but the exact result depends on Python version, platform, attribute count, and the rest of the object graph.
Recipe 1: Measure Before Changing the Class
import sys import tracemalloc class RegularPoint: 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 def build(cls, n=100_000): return [cls(i, i + 1) for i in range(n)] for cls in (RegularPoint, SlottedPoint): tracemalloc.start() points = build(cls) current, peak = tracemalloc.get_traced_memory() print(cls.__name__, f"{peak / 1024 / 1024:.2f} MiB") print("instance size:", sys.getsizeof(points[0])) tracemalloc.stop()
Measure the whole workload, not just sys.getsizeof(instance). A regular object may keep the dictionary separately, and referenced values have their own sizes.
Recipe 2: Convert a Fixed-Shape Class
class Particle: __slots__ = ("x", "y", "vx", "vy", "mass") def __init__(self, x, y, vx, vy, mass): self.x = x self.y = y self.vx = vx self.vy = vy self.mass = mass
This class now supports only the declared instance attributes. That is useful when the shape is part of the contract. It is wrong when callers are expected to attach extra fields for caching, instrumentation, or framework state.
Recipe 3: Prefer dataclass(slots=True) for Data Containers
from dataclasses import dataclass @dataclass(slots=True) class Particle: x: float y: float vx: float = 0.0 vy: float = 0.0 mass: float = 1.0
For simple data containers, slots=True keeps the field declaration in one place. It also avoids the common mistake of trying to define class-level defaults with the same names as slots.
Keeping Dynamic Attributes or Weak References
class HybridPoint: __slots__ = ("x", "y", "__dict__") def __init__(self, x, y): self.x = x self.y = y point = HybridPoint(1, 2) point.label = "origin" # stored in __dict__
Add __dict__ only when dynamic attributes are intentionally part of the API. Add __weakref__ when instances must work with weakref.ref(), weak caches, or observer registries.
Inheritance Rules
class Base: __slots__ = ("created_at",) class Child(Base): __slots__ = ("name",) class Marker(Base): __slots__ = ()
Slots declared in a parent are available to child instances. A child that adds fields should declare only the additional slot names. A child that adds no fields can use __slots__ = () to avoid bringing back an instance dictionary.
Common Pitfalls
Slot Names Are Not Class Defaults
class Wrong: __slots__ = ("x",) # x = 10 # would conflict with the slot descriptor class Right: __slots__ = ("x",) def __init__(self, x=10): self.x = x
Multiple Inheritance Has Layout Limits
Only one parent class in a multiple-inheritance hierarchy can contribute non-empty slot storage. Other slotted bases need empty layouts such as __slots__ = ().
Slots Are Not Packed Numeric Arrays
Slots store Python object references. For dense numeric data, arrays, NumPy, struct, or extension types may be a better representation.
Decision Table
| Situation | Use __slots__? | Reason |
|---|---|---|
| Many small objects with fixed attributes | Yes, after measuring | Removes per-instance dictionary overhead |
| Objects need arbitrary attributes | No | Dynamic assignment is part of the behavior |
| Simple annotated data container | Prefer @dataclass(slots=True) | Less duplication and fewer descriptor mistakes |
| Need weak references | Yes, with __weakref__ | Weak references are not automatic for slotted classes |
| Dense numeric vectors | Usually no | Slots still store Python object references |
