Back to Blog
Python

Python Slots Memory Optimization: Reduce Instance Memory

Learn how Python's `__slots__` reduces per-instance memory, when to use it, and the tradeoffs with inheritance, dynamic attributes, and weak references.

__slots__memory optimizationPython performanceinstance attributesPython classes
Diagram showing a Python instance with __slots__ replacing the instance dictionary to reduce memory footprint.

Python instances store their attributes in a per-instance dictionary by default. That dictionary gives flexibility—you can add or remove attributes at runtime—but it costs memory. For applications that create millions of small objects, that overhead can dominate. __slots__ addresses this by replacing the per-instance dictionary with a fixed set of slot descriptors, reducing memory per instance and often speeding up attribute access.

How Python Stores Instance Attributes by Default

When you define a class without any special declarations, each instance gets its own __dict__ attribute. This is a regular Python dictionary that maps attribute names to values. The dictionary is the reason you can do this:

class Point: def __init__(self, x, y): self.x = x self.y = y p = Point(1, 2) p.z = 3 # works fine

The ability to add z after the instance exists is convenient, but the dictionary itself has significant overhead. Each dictionary entry stores the key, the value, a hash, and internal metadata. Because of that overhead, even a small object such as a 2D point costs more memory than the raw attribute values would suggest. When your program holds thousands or millions of such objects, the memory cost becomes a real concern.

Python does not provide a way to disable __dict__ from outside the class; the standard mechanism is to declare __slots__ in the class definition.

What slots Does

__slots__ is a class-level declaration that tells Python exactly which attributes an instance may have. When you define __slots__, Python creates a descriptor for each declared name, and instances store those values in a compact internal array instead of a per-instance __dict__. The result is a significant reduction in per-instance memory.

The tradeoff is that you lose the ability to add arbitrary attributes. If you try to assign an attribute not listed in __slots__, Python raises AttributeError. This is often acceptable for data-holding classes where the set of attributes is known in advance.

Declaring slots in a Class

The syntax is simple: define a class attribute __slots__ as a sequence of strings. The most common form is a tuple of attribute names.

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

Now Point instances have no __dict__. They still support attribute access and assignment for x and y, but trying to set a new attribute fails:

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

The __slots__ declaration can also be a list, but a tuple is conventional and signals that the set of attribute names is fixed. You can include methods and other class attributes normally; __slots__ only affects instance attributes.

Memory Savings and When They Matter

The memory reduction comes from eliminating the per-instance dictionary. The exact savings depend on the Python implementation, the number of attributes, and the class layout, but for small objects the effect is often substantial when you create many instances. Typical scenarios include:

  • Data structures like points, vectors, and graph nodes.
  • ORM models where each row becomes an object.
  • Configuration entries or parsed tokens.
  • Large lists of objects in memory-heavy applications.

If your program creates only a handful of instances, the savings are negligible. But when you have millions of objects, the difference can be the reason your process fits in available RAM or does not.

To measure the effect in your own code, start with sys.getsizeof() on an instance. For a class without __slots__, the instance object carries a pointer to a separately allocated dictionary, so also check sys.getsizeof(instance.__dict__) when a __dict__ exists. A slots instance without '__dict__' in its __slots__ has no __dict__, so that extra allocation disappears. sys.getsizeof() does not account for referenced objects, such as the integer values themselves.

Inheritance and slots

Inheritance adds a few important rules. If a base class defines __slots__, a subclass that wants to keep the no-dict layout must also define __slots__, even if it adds no new instance attributes. Use __slots__ = () in that case. If the subclass adds new attributes, list only the new attribute names in its own __slots__.

class Base: __slots__ = ('a',) class Child(Base): __slots__ = ('b',) # necessary to avoid __dict__ c = Child() c.a = 1 c.b = 2

If you omit __slots__ in Child, instances of Child will still have the slot for a, but they will also have a __dict__, and b is stored there. The no-dict memory benefit is lost for those instances.

Multiple inheritance with __slots__ is more restrictive. In CPython, combining two parent classes that both define nonempty __slots__ fails at class creation with TypeError: multiple bases have instance lay-out conflict. This is rare, but it matters if you are combining mixins that each define slots.

Limitations: Dynamic Attributes and Weak References

Two common features are disabled when you use __slots__ unless you explicitly opt in.

First, instances no longer support arbitrary attribute assignment. If your code relies on setting attributes dynamically—for example, attaching metadata to an object—__slots__ will break that pattern. You can work around it by adding '__dict__' to __slots__, but that reintroduces a per-instance dictionary and reduces the memory benefit.

Second, instances cannot be used with weakref.ref unless you include '__weakref__' in __slots__. This is because the weak reference machinery needs a slot to store the reference. If you need weak references, add '__weakref__' to your __slots__ tuple.

class Node: __slots__ = ('value', '__weakref__')

These limitations are not bugs; they are the price of a fixed memory layout. You should only use __slots__ when you know the attribute set is stable and you do not need dynamic assignment or weak references.

Comparing slots with Other Memory-Saving Options

__slots__ is not the only way to reduce memory for data-holding classes. Two alternatives are worth considering.

namedtuple from the collections module creates compact tuple-based classes with named fields. Instances do not have a per-instance __dict__, so memory usage is low. The tradeoff is that tuples are immutable and adding custom methods typically requires subclassing, so namedtuple is less flexible than a regular class.

Python 3.7+ introduced dataclasses. By default, a dataclass still uses __dict__ for instance storage. But you can pass slots=True to the @dataclass decorator; the slots parameter is available since Python 3.10. This generates a class that uses __slots__ automatically.

from dataclasses import dataclass @dataclass(slots=True) class Point: x: int y: int

This is often the cleanest way to get the benefit of __slots__ without writing the boilerplate manually. The generated class behaves like a normal dataclass, but its instances have no __dict__.

If you are already using dataclasses, switching to slots=True is a low-effort change. If you are writing a plain class, adding __slots__ manually is straightforward.

Performance Considerations Beyond Memory

__slots__ also affects attribute access speed. Slot descriptors access instance storage by offset rather than through a dictionary lookup, so attribute access can be faster in practice. The difference is usually small and may not matter for most code. The primary benefit remains memory.

There is a subtle performance caveat with __slots__ and inheritance: if a deep inheritance chain defines slots at multiple levels, attribute lookup may involve more type traversal. This is rarely a problem, but it matters if you are optimizing at the micro level.

Another consideration is that __slots__ can make class creation slightly slower because Python has to set up the descriptors. This is a one-time cost per class, not per instance, so it is negligible unless you are dynamically generating many classes.

When to Use slots in Production

Use __slots__ when you have a class that represents a fixed data record, you expect to create many instances, and you do not need dynamic attributes or weak references. Common examples are value objects, DTOs, and lightweight data models.

Avoid __slots__ when the class is part of a public API where users might want to add attributes, when you rely on weak references, or when you are not sure the attribute set will remain stable. The AttributeError that results from a missing slot can be confusing if the code is not designed for it.

If you are using dataclasses, prefer slots=True for new code. For existing code, measure the memory usage first to see if the change is worth the effort. A simple script that creates a million instances and reports sys.getsizeof() and total process memory can tell you whether __slots__ will help your specific workload.

Python Slots Memory Optimization: Practical Usage and Code Examples | RYUSLOG DEV