Back to Blog
Python

Python Slots: Save Memory and Speed Up Attribute Access

Learn how Python's __slots__ removes the per-instance __dict__, when inheritance silently reintroduces it, and when slots are a good design choice.

__slots__memory optimizationPython classesperformanceobject model
Editorial illustration showing a compact slotted Python class object next to a bulky dictionary, highlighting memory savings

Python's __slots__ is a class-level declaration that replaces the per-instance __dict__ with a fixed set of descriptors. It is a useful technique when you have many instances of a data-heavy class, but it comes with tradeoffs: dynamic attribute assignment no longer works, and inheritance can silently undo the memory savings.

What __slots__ Does

In Python, every instance of a regular class carries a __dict__ — a per-instance dictionary that maps attribute names to values. That dictionary is what makes Python attribute assignment so flexible, but it also costs memory: each instance pays for the dict object itself plus the storage overhead of its keys and entries. Python's slots mechanism, declared with __slots__, changes that tradeoff.

When a class declares __slots__, instances no longer get a __dict__. Instead, the class defines a fixed set of attribute descriptors, and each instance stores those values in a compact internal layout. Attribute access avoids a dict lookup, so it is often slightly faster, and each instance uses less memory.

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

With this definition, Point instances can only hold x and y. Trying to assign p.z = 3 raises AttributeError. That restriction is the tradeoff that buys the memory savings.

Basic Syntax and Rules

__slots__ is a class attribute that can be a tuple, list, or any iterable of strings. Convention is to use a tuple, since it signals that the attribute set is fixed.

class Vector3: __slots__ = ('x', 'y', 'z')

A few rules follow from the way slots work:

  • Every name in __slots__ becomes a descriptor on the class.
  • You cannot assign an attribute that is not listed in __slots__.
  • Default values must be set in __init__; a class-level attribute with the same name as a slot conflicts with the slot descriptor.
  • Slots can coexist with properties and other descriptors, as long as the slot name does not collide with the descriptor name.

If you define a property named radius and also put 'radius' in __slots__, class creation raises a ValueError because the name conflicts with the property. Use a private slot and expose the public value through the property:

class Circle: __slots__ = ('_radius',) def __init__(self, radius): self._radius = radius @property def radius(self): return self._radius @radius.setter def radius(self, value): self._radius = value

Why Slots Save Memory

The key mechanism is the absence of __dict__. A regular instance holds a dict that hashes attribute names, stores entries, and overallocates to accommodate future insertions. For a small object with a handful of attributes, the dict overhead can dwarf the actual data.

With slots, the instance stores values in a fixed layout — essentially an array of pointers at the C level. No dict, no key hashing, no overallocation. The savings are most pronounced for classes with many live instances: data records, vector types, configuration objects, tree nodes, and similar cases.

Attribute access also changes. With __dict__, lookup goes through the instance dict. With slots, the descriptor on the class handles the access directly, which is why slot access is usually slightly faster.

Inheritance and Slots

Slots do not propagate cleanly through inheritance. A subclass that does not define __slots__ gets its own __dict__. The base class's slots still exist, but the subclass instances now carry both the slot storage and a dict, which defeats the memory savings.

class Point3D(Point): def __init__(self, x, y, z): super().__init__(x, y) self.z = z

Here Point3D instances have __dict__ because Point3D does not declare __slots__. To preserve the savings, the subclass must also declare slots:

class Point3D(Point): __slots__ = ('z',)

Note the trailing comma. ('z',) is a tuple; ('z') is just a string. A string is iterable, so passing a bare string to __slots__ would create one slot per character. For a multi-character name like 'name', that would silently produce slots n, a, m, and e instead of a slot named 'name'.

Multiple inheritance with nonempty slots is more restrictive. If two base classes both define nonempty __slots__, Python raises TypeError because the layouts cannot be merged. Empty slots (__slots__ = ()) do not cause this problem, which is why some mixin classes declare empty slots to avoid forcing a __dict__ onto subclasses.

Limitations and Edge Cases

The main limitation is the loss of dynamic attributes. Code that adds attributes at runtime will raise AttributeError unless those names are already in __slots__.

Other behaviors worth knowing:

  • __dict__ disappears unless you explicitly add '__dict__' to __slots__, which reintroduces the memory cost.
  • Weak reference support also disappears unless '__weakref__' is in __slots__. If code uses weakref.ref on instances of the class, the slot must be declared.
  • On Python 3, a slot name that collides with a property or another class-level descriptor raises a ValueError when the class is created. Keep slot and descriptor names distinct.
  • Pickling slotted instances works in Python 3, but custom __reduce__ or __getstate__ implementations that assume a __dict__ will need adjustment.

These limitations are not bugs; they are the direct consequence of removing the instance dict. Any code path that assumes a dict exists will need a different approach.

When to Use Slots

Use slots when the class is a plain data holder with a fixed schema and you create many instances. Data records, vector types, configuration objects, and tree nodes are typical candidates.

Do not use slots when you need dynamic attributes, when you rely on __dict__ introspection, or when the class is part of a framework that expects a dict. Some ORMs, serializers, and mixin libraries inspect __dict__ directly; a slotted class will break them in ways that are hard to trace.

A practical rule: if a class is a stable data container, slots are a low-cost win. If the class is an abstraction boundary with dynamic behavior, keep the dict.

Performance Considerations

The memory savings are real, but the exact numbers depend on the object size and the Python version. The mechanism is what matters: slots eliminate the per-instance dict and its overallocation.

Attribute access is also slightly faster with slots because it avoids the dict lookup path. For hot loops that touch many attributes on many objects, that can add up, but it is rarely the primary reason to use slots. Memory is the stronger argument.

There is a maintainability cost: adding a new attribute requires updating __slots__. For long-lived classes, that friction accumulates. The tradeoff is usually worth it for data-heavy code and not worth it for general-purpose classes.

Python Slots: Practical Usage and Tradeoffs | RYUSLOG DEV