Back to Blog
Python

Python Slots: Save Memory and Speed Up Attribute Access

python slots: Learn how Python's __slots__ removes the per-instance __dict__, when inheritance breaks the savings, and how to decide if slots fit your class design.

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

python slots requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

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. The result is that attribute access is faster and each instance uses significantly 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__; slots do not support class-level defaults the way regular attributes do.
  • Slots can coexist with properties and other descriptors, as long as the slot name does not collide with the descriptor name.

The last point matters more than it looks. If you define a property named radius and also put "radius" in __slots__, the slot descriptor shadows the property, and the property's getter and setter never run.

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 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 relies on hasattr, setattr, or duck typing by adding attributes at runtime will break with AttributeError.

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.
  • A slot name that collides with a class-level descriptor, such as a property, silently shadows the descriptor.
  • 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 Code Examples | RYUSLOG DEV