Python Slots Memory Optimization: Reduce Instance Memory
python slots memory optimization: Learn how __slots__ reduces per-instance memory in Python, when to use it, and the tradeoffs with inheritance and dynamic attributes.
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. python slots memory optimization addresses this by replacing the instance dictionary with a fixed set of 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. Even a small object like a 2D point can consume over 100 bytes of dictionary overhead alone, in addition to the two integer values. 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__ without changing the class definition. The standard mechanism for doing so is __slots__.
What slots Does
__slots__ is a class-level declaration that tells Python exactly which attributes an instance may have. When you define __slots__, Python no longer creates a __dict__ for each instance. Instead, each declared attribute becomes a descriptor that stores its value in a compact internal structure. 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 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 number of attributes and the Python implementation, but it is common to see memory usage drop by 30–50% for small objects. The savings are most pronounced when you have many instances of the same class. 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, you can use sys.getsizeof() on an instance. For a class without __slots__, the size includes the __dict__; with __slots__, the size is smaller. Note that sys.getsizeof() does not account for the referenced objects (like the integers themselves), but it does show the per-instance container overhead.
Inheritance and slots
Inheritance adds a few important rules. If a base class defines __slots__, subclasses must also define __slots__ if they add new instance attributes. If a subclass does not define __slots__, it will get a __dict__ again, negating the memory savings for that subclass.
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 have both the slot for a and a __dict__ for b and any other attributes. The memory benefit is lost.
Multiple inheritance with __slots__ is more restrictive. You cannot have two parent classes with nonempty __slots__ that define the same attribute name. Python will raise a TypeError at class creation. This is a rare case, 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 the dictionary overhead, defeating the purpose.
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 tuple-based classes with named fields. Tuples are compact, but they are immutable and do not support custom methods easily without subclassing. namedtuple instances also have a __dict__ by default? Actually, namedtuple does not create a __dict__; it uses a tuple for storage, so memory is low. However, you cannot add methods without subclassing, and the class is not as flexible as 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 (available since Python 3.10) to generate a class with __slots__ automatically. This combines the convenience of dataclass syntax with the memory savings of __slots__.
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. Because the attribute names are known at class creation, Python can use descriptors that directly index into a compact array, avoiding the hash lookup required for a dictionary. In practice, attribute access is often faster, though the difference is small and may not matter for most code. The primary benefit remains memory.
There is a subtle performance tradeoff with __slots__ and inheritance. If you have a deep inheritance chain where each class defines __slots__, attribute lookup may be slightly slower because Python must traverse the MRO to find the descriptor. This is rarely a problem, but it is worth knowing 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 memory can tell you whether __slots__ will help your specific workload.