__slots__ Optimization — Core Concepts

What slots Actually Does

Every Python object normally carries a __dict__ — a dictionary that stores its attributes. Dictionaries are powerful but expensive: each one allocates a hash table, which consumes roughly 100-200 bytes even when nearly empty.

When you define __slots__ on a class, Python replaces __dict__ with fixed-size slots — essentially C-level struct fields. The object stores references in a compact array rather than a hash table.

class Point:
    __slots__ = ('x', 'y')
    
    def __init__(self, x, y):
        self.x = x
        self.y = y

This Point uses about 56 bytes per instance, compared to ~152 bytes without slots — a 63% reduction.

When the Savings Matter

The per-object savings seem small, but they compound:

ObjectsWithout slotsWith slotsSavings
1,000152 KB56 KB96 KB
1,000,000152 MB56 MB96 MB
10,000,0001.52 GB560 MB~1 GB

Real-world examples where this matters:

  • ORM model instances (Django, SQLAlchemy rows)
  • Graph nodes in network analysis
  • Coordinate points in geospatial data
  • Tokens/nodes in parsers and AST walkers

The Rules

You can’t add new attributes

p = Point(1, 2)
p.z = 3  # AttributeError: 'Point' object has no attribute 'z'

This is the fundamental trade-off. With __dict__, objects are bags of attributes. With __slots__, they’re fixed structures.

Inheritance gets tricky

If a parent class doesn’t define __slots__, the child still gets __dict__ through the parent. To get the full memory benefit, every class in the chain needs __slots__.

class Base:
    __slots__ = ('a',)

class Child(Base):
    __slots__ = ('b',)  # Only 'b' here — 'a' comes from Base

If Child omits __slots__, it regains __dict__ and you lose the memory savings.

No default values in slots

__slots__ defines attribute names, not values. Default values go in __init__ or as class-level descriptors (which is an advanced pattern).

slots and dict can coexist

If you include '__dict__' in your slots tuple, the object gets both fixed slots and a dynamic dictionary:

class Hybrid:
    __slots__ = ('x', 'y', '__dict__')

This gives you fast access for known attributes and flexibility for unknown ones. It’s occasionally useful but defeats much of the memory purpose.

Speed Benefits

Attribute access on slotted objects is slightly faster because Python doesn’t need to hash the attribute name and look it up in a dictionary. It uses a direct offset into the object’s memory.

The speed difference is roughly 10-20% for attribute access. In practice, this only matters in extremely hot loops — the memory savings are usually the primary motivation.

Common Misconception

slots makes my class immutable.” No. Slotted attributes are still fully mutable — you just can’t add new ones. You can change point.x freely; you just can’t add point.z.

For actual immutability, use @dataclass(frozen=True) or NamedTuple.

When to Use slots

✅ You’re creating many instances of the same class (thousands+) ✅ You know all the attributes at design time ✅ Memory profiling shows object overhead is significant ✅ You’re building data containers with fixed schemas

When to Skip Them

❌ You have few instances ❌ You need dynamic attributes (plugin systems, monkey-patching) ❌ You’re subclassing classes without slots (benefit is lost) ❌ You’re using dataclasses or NamedTuples (they handle optimization differently)

One thing to remember: __slots__ is a memory optimization, not a design pattern. Profile first, slot second. When you have millions of objects with known attributes, it’s one of the easiest wins in Python.

pythonperformanceoop

See Also