Skip to main content

Python __slots__ Optimization

Learn when Python __slots__ reduces memory, how slot storage differs from __dict__, and the caveats for dataclasses and inheritance.

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.

Loading visualization...

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

SituationUse __slots__?Reason
Many small objects with fixed attributesYes, after measuringRemoves per-instance dictionary overhead
Objects need arbitrary attributesNoDynamic assignment is part of the behavior
Simple annotated data containerPrefer @dataclass(slots=True)Less duplication and fewer descriptor mistakes
Need weak referencesYes, with __weakref__Weak references are not automatic for slotted classes
Dense numeric vectorsUsually noSlots still store Python object references

If you found this explanation helpful, consider sharing it with others.

Mastodon