Python __slots__: Memory Savings and Faster Access
python **slots**: Learn how Python's __slots__ reduces per-instance memory and speeds up attribute access by replacing the instance dictionary with fixed descriptors.
When you define a normal Python class, each instance carries a __dict__ that maps attribute names to values. That dictionary gives you flexibility—you can add attributes at runtime—but it also adds memory overhead per instance. Python's __slots__ lets you declare a fixed set of attributes, and the interpreter then stores those attributes in a compact internal structure instead of a per-instance dictionary. This article explains how python **slots** works, when it helps, and where it can cause problems if applied carelessly.
How slots Changes Instance Storage
A regular class stores instance attributes in a __dict__. That dictionary is a hash table, which is efficient for dynamic attribute lookup but relatively large even for a single attribute. When you define __slots__ in a class, Python reserves a fixed amount of space for each declared attribute and stores the values directly on the instance using descriptors. The per-instance __dict__ is no longer created, and the instance becomes smaller.
Consider a simple class without slots:
class Point: def __init__(self, x, y): self.x = x self.y = y
Each Point instance has a __dict__ that holds x and y. The dictionary itself consumes memory beyond the two values. With __slots__, the same class looks like this:
class Point: __slots__ = ('x', 'y') def __init__(self, x, y): self.x = x self.y = y
Now the instance does not have a __dict__. The attributes are stored in a more compact internal layout. The exact memory savings depend on the number of attributes and the Python implementation, but the mechanism is consistent: no per-instance dictionary means less overhead.
Defining slots in a Class
The value of __slots__ can be a tuple, a list, or a string containing space-separated attribute names. A tuple is the conventional choice because it signals that the attribute set is fixed. The names must be strings and must not contain a leading underscore unless you intend to use name mangling.
class Vector: __slots__ = ('x', 'y', 'z') def __init__(self, x, y, z): self.x = x self.y = y self.z = z
Once __slots__ is defined, you cannot assign attributes that are not listed. Attempting to do so raises AttributeError. This restriction is intentional: it enforces a fixed schema for instances and is the reason the interpreter can avoid the dictionary.
v = Vector(1, 2, 3) v.magnitude = 5 # AttributeError: 'Vector' object has no attribute 'magnitude'
If you need to keep the ability to add arbitrary attributes, __slots__ is not the right tool. The tradeoff is between dynamic flexibility and memory efficiency.
Inheritance and slots
When a subclass inherits from a class that uses __slots__, the subclass does not automatically inherit the slot descriptors. If the subclass does not define its own __slots__, it will get a __dict__ and the parent's slots still work, but the memory savings are lost because the subclass instances now carry a dictionary.
class Base: __slots__ = ('a',) class Child(Base): pass c = Child() c.b = 10 # Works because Child has a __dict__
To preserve the slot behavior, the subclass must also define __slots__:
class Child(Base): __slots__ = ('b',) c = Child() c.a = 1 c.b = 2 c.c = 3 # AttributeError
Multiple inheritance with __slots__ is more restrictive. If two parent classes both define __slots__, they may conflict unless the slot names are disjoint. The interpreter will raise an error if two parent slots share the same name, because each descriptor would try to occupy the same offset. In practice, you should design slot-based classes with inheritance carefully, or avoid mixing slot classes in multiple inheritance.
Attribute Access and Memory Behavior
The primary benefit of __slots__ is memory reduction. Because each instance no longer owns a dictionary, the per-instance footprint shrinks substantially when you have many instances. This matters in scenarios like game entity systems, large data processing pipelines, or long-running services holding many small objects.
Attribute access also changes. With a normal class, reading obj.attr involves a dictionary lookup. With slots, the attribute is retrieved through a descriptor that knows its fixed offset, which can be faster because it avoids hashing the attribute name and probing the dictionary. The exact speed difference depends on the Python implementation and the number of attributes, but the mechanism is deterministic: no dictionary lookup is required.
One subtlety is that __slots__ also removes the __weakref__ attribute by default. If you need to create weak references to instances, you must include '__weakref__' in the __slots__ tuple:
class CacheEntry: __slots__ = ('key', 'value', '__weakref__')
Without this, calling weakref.ref(instance) will raise TypeError. This is a common oversight when converting existing classes to use slots.
When to Use slots (and When Not To)
Use __slots__ when you have a class that will be instantiated many times and the set of attributes is known in advance. Typical candidates are data transfer objects, configuration records, and lightweight value objects. If the class is part of a public API and users might want to add custom attributes, slots can be too restrictive.
Avoid __slots__ when you need dynamic attributes, such as when the class is a proxy or a wrapper that stores arbitrary state. Also avoid it if you rely on __dict__ introspection, for example when serializing objects with vars() or when using tools that expect a per-instance dictionary. Some debugging and profiling utilities assume __dict__ exists; they will fail or behave differently with slot-based instances.
If you are using Python 3.10 or later, the dataclass decorator accepts a slots=True parameter, which generates a class with __slots__ automatically. This is often the cleanest way to get the memory benefits without writing the slot declaration by hand.
from dataclasses import dataclass @dataclass(slots=True) class Point: x: float y: float
This approach combines the convenience of dataclasses with the memory efficiency of slots, and it handles inheritance correctly as long as all base classes also use slots.
Common Pitfalls and Edge Cases
One common mistake is forgetting that __slots__ prevents the creation of a __dict__. If you later try to set an attribute that was not declared, you get an AttributeError. This can be surprising when a class is extended by a subclass that does not declare slots.
Another pitfall involves class-level attributes. If you define a class attribute with the same name as a slot, the slot descriptor overrides it. The instance value will be stored in the slot, and the class attribute is shadowed. This can lead to confusion if you intend to use a class-level default.
class A: __slots__ = ('x',) x = 10 a = A() a.x = 20 print(A.x) # 10 print(a.x) # 20
Slots also interact with pickling and copying. By default, copy.copy and copy.deepcopy work with slots, but pickle may require you to implement __getstate__ and __setstate__ if the class uses custom serialization. The built-in pickle protocol can handle slots if the class defines __slots__ and the pickler knows how to access them, but it is not as automatic as with a __dict__.
Finally, be aware that __slots__ does not reduce memory for the class itself; it only affects instances. If you have a class that is instantiated rarely, the benefit is negligible. The overhead of defining the slot descriptors is small, but the real gain appears when you create thousands or millions of instances.
Alternatives: dataclasses and namedtuples
Before adopting __slots__, consider whether a simpler alternative already fits. namedtuple from the collections module creates tuple-based classes with fixed fields and no per-instance dictionary. They are immutable and memory-efficient, but they lack type annotations and are not easy to extend with methods.
Dataclasses with slots=True combine the declarative style of dataclasses with the memory benefits of slots. They also support default values, type hints, and generated methods like __repr__ and __eq__. If you are starting a new class that fits a data-holder pattern, this is often the best choice.
For classes that need mutable state and a fixed attribute set, manual __slots__ remains a direct and transparent approach. The decision comes down to how much structure you want: a plain class with slots gives you full control, while a dataclass with slots adds convenience at the cost of some generated boilerplate.
When you choose __slots__, you are making a deliberate tradeoff. You give up dynamic attribute assignment and some introspection capabilities in exchange for lower memory usage and potentially faster attribute access. That tradeoff is worthwhile for high-volume, fixed-schema objects, but it is not a universal optimization. Measure your actual memory and performance needs before applying slots across a codebase, and always verify that the class does not rely on features that slots remove.