If you have worked on medium to large Python projects, you have likely faced excessive memory consumption issues. Every Python object carries a dictionary (__dict__) that stores its attributes dynamically, a flexible mechanism that comes with a significant cost. This is where __slots__ steps in as an elegant and powerful optimization solution.
In this complete guide, you will learn what __slots__ are, how they work under the hood, when to use them, and what pitfalls to avoid. Everything comes with practical examples and real benchmarks so you can apply these techniques immediately in your projects.
The Problem: Memory Usage in Python Classes
To understand the value of __slots__, we first need to grasp how Python manages instance attributes. By default, every Python class stores its attributes in a dictionary called __dict__. This dictionary allows you to add, remove, and modify attributes dynamically, a feature that makes Python incredibly flexible.
The downside is that dictionaries are heavyweight data structures. Each dictionary entry includes the key, value, key hash, and internal metadata. When you have thousands or millions of objects, each object's __dict__ consumes a significant amount of memory. As the official Python documentation explains, declaring __slots__ replaces __dict__ with a more efficient storage structure.
class WithoutSlots:
def __init__(self, x, y):
self.x = x
self.y = y
obj = WithoutSlots(10, 20)
print(obj.dict) # {'x': 10, 'y': 20}
print(sys.getsizeof(obj)) # 56 bytes (excluding dict)
The __dict__ dictionary adds roughly 120 bytes for a simple object with two attributes, and this overhead grows as you add more attributes. In applications that create many objects, such as games, scientific simulations, large-scale data processing, or high-performance servers, this overhead becomes a significant bottleneck.
What Are __slots__?
__slots__ is a special class attribute that explicitly defines which instance attributes a class can have. When you declare __slots__, Python stops creating the __dict__ dictionary for each instance and instead allocates only the space needed for the listed attributes. The Python Wiki describes this technique as a way to reduce memory consumption by 50% or more.
class WithSlots:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
obj2 = WithSlots(10, 20)
print(obj2.dict) # AttributeError: 'WithSlots' object has no attribute 'dict'
print(sys.getsizeof(obj2)) # 48 bytes (total, no extra dict)
The syntax is straightforward: define a tuple (or list) with the names of the allowed attributes. Each instance then stores these values in a compact fixed-size array instead of a full dictionary.
How __slots__ Work Internally
To truly master __slots__, it helps to understand the internal mechanism. When Python compiles a class that defines __slots__, it creates descriptors for each listed name. These descriptors manage access to values stored in numbered slots within an internal instance array.
This means attribute access in classes with __slots__ is not only more memory-efficient but also faster. The official Python classes tutorial provides an excellent overview of this behavior.
class Point:
__slots__ = ('x', 'y', 'z')
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
p = Point(1, 2, 3)
print(p.x) # 1
p.w = 4 # AttributeError: 'Point' object has no attribute 'w'
Beyond memory savings, restricting attribute creation to only those in __slots__ acts as a safeguard against typos. If you accidentally write self.x = 1 instead of self.x = 1, Python immediately raises an AttributeError instead of silently creating a wrong new attribute.
Practical Examples
Let us explore real-world scenarios where __slots__ makes a significant difference.
Particle System for Games
In a 2D game, you might have thousands of particles on screen simultaneously. Each particle needs to store position, velocity, color, lifetime, and other attributes. Without __slots__, the memory consumed by these particles can quickly spiral out of control.
class Particle:
__slots__ = ('x', 'y', 'vx', 'vy', 'color', 'life', 'size')
def __init__(self, x, y, vx, vy, color, life, size):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.color = color
self.life = life
self.size = size
particles = [Particle(i, i*2, 1, -1, 'blue', 100, 3) for i in range(100000)]
With __slots__, each particle takes about 80 bytes. Without __slots__, each would consume roughly 200 bytes or more. For 100 thousand particles, the savings amount to about 12 MB. The memory-profiler is a useful tool for measuring this difference in your projects.
Data Records
Imagine you are processing a CSV file with millions of rows and representing each row as a Python object. Using __slots__ drastically reduces memory usage.
class SaleRecord:
__slots__ = ('product', 'quantity', 'price', 'date', 'seller')
def __init__(self, product, quantity, price, date, seller):
self.product = product
self.quantity = quantity
self.price = price
self.date = date
self.seller = seller</code></pre>
This approach is especially useful when combined with on-demand reading of large files, where each line is processed and kept in memory for a short period. The memory savings accumulate and can mean the difference between your program running smoothly or hitting the RAM limit.
Memory and Performance Benchmarks
Let us objectively compare classes with and without __slots__ using the sys.getsizeof function and a simple attribute access speed test.
import sys
import time
class ClassWithoutSlots:
def init(self, a, b, c):
self.a = a
self.b = b
self.c = c
class ClassWithSlots:
slots = ('a', 'b', 'c')
def init(self, a, b, c):
self.a = a
self.b = b
self.c = c
Memory comparison
obj1 = ClassWithoutSlots(1, 2, 3)
obj2 = ClassWithSlots(1, 2, 3)
print(f"Without slots: {sys.getsizeof(obj1)} bytes")
print(f"With slots: {sys.getsizeof(obj2)} bytes")
dict adds more memory
dict_size = sys.getsizeof(obj1.dict)
print(f"Extra dict: {dict_size} bytes")
Typical results show that objects with __slots__ consume 40 to 60% less memory. In attribute access speed tests, the difference is also noticeable: objects with __slots__ are generally 10 to 15% faster for reading and writing attributes.
When to Use __slots__
__slots__ is not a universal solution. There are specific scenarios where it shines and others where its use is unnecessary or even detrimental. The Stack Overflow community has excellent discussions on the pros and cons of using __slots__.
Use __slots__ when:
- You create thousands or millions of objects from the same class
- Each object has a fixed, well-defined set of attributes
- Memory is a critical resource (games, embedded systems, high-throughput servers)
- Attribute access performance matters
- You want to prevent accidental creation of new attributes
Avoid __slots__ when:
- You need to add attributes dynamically
- Your classes make heavy use of multiple inheritance
- You rely on weak references without including
__weakref__ in slots
- The number of class instances is small (hundreds or a few thousand)
Limitations and Caveats
Despite the benefits, __slots__ imposes important limitations you need to know before adopting it in your projects.
1. No instance dictionary: As mentioned, objects with __slots__ have no __dict__. This means you cannot add new attributes dynamically after object creation. If you try, you will get an AttributeError.
2. No weak references by default: Objects with __slots__ do not support weakref unless you explicitly include '__weakref__' in the slots tuple.
class WithWeakRef:
__slots__ = ('x', '__weakref__')
def __init__(self, x):
self.x = x
3. Inheritance: Subclasses of classes with __slots__ also need to define their own __slots__ to optimize memory. If a subclass does not define __slots__, it will have a __dict__ even if the parent class uses __slots__. The Python data model documentation explains this behavior in detail.
class Base:
__slots__ = ('a',)
class Derived(Base):
slots = ('b',)
d = Derived()
d.a = 1 # OK
d.b = 2 # OK
d.c = 3 # AttributeError
__slots__ and Inheritance: Best Practices
Inheritance with __slots__ requires extra attention. When you inherit from a class that uses __slots__, the subclass must declare its own __slots__ including only the attributes it adds, not those from the parent class. Python automatically manages space for attributes at all hierarchy levels.
class Vehicle:
__slots__ = ('make', 'model', 'year')
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
class Car(Vehicle):
slots = ('doors',)
def __init__(self, make, model, year, doors):
super().__init__(make, model, year)
self.doors = doors</code></pre>
A common pitfall is forgetting to define __slots__ in the subclass. In that case, the subclass will have a __dict__ and will not benefit from memory optimization, even if the parent class uses __slots__.
# WRONG: No __slots__ in the subclass
class Car2(Vehicle):
def __init__(self, make, model, year, doors):
super().__init__(make, model, year)
self.doors = doors
c2 = Car2('VW', 'Gol', 2024, 4)
print(hasattr(c2, 'dict')) # True! Has dict even though parent uses slots
__slots__ and the Descriptor Protocol
Internally, each name in __slots__ creates a descriptor on the class. Descriptors are objects that manage attribute access through the __get__, __set__, and __delete__ methods. This means you can combine __slots__ with @property for fine-grained control over data input and output.
class User:
__slots__ = ('_name', '_email')
def __init__(self, name, email):
self._name = name
self._email = email
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not value.strip():
raise ValueError("Name cannot be empty")
self._name = value</code></pre>
This combination lets you have the efficiency of __slots__ without giving up encapsulation best practices. For more details on @property, check our complete guide on this topic. [link_interno_2]
Alternatives to __slots__
Depending on your use case, other structures may be more suitable than __slots__:
namedtuple: Creates immutable classes with named attribute access. Memory-efficient, but does not easily support custom methods.
dataclass: Since Python 3.7, the dataclasses module lets you create classes with concise syntax. You can combine dataclass with __slots__ using slots=True starting from Python 3.10.
from dataclasses import dataclass
@dataclass(slots=True)
class Config:
host: str
port: int
timeout: float
Plain dict or types.SimpleNamespace: For objects that are mere data containers, a dictionary or SimpleNamespace may suffice. The Real Python tutorial on __slots__ compares these alternatives in detail.
NumPy arrays: For homogeneous numerical data, NumPy arrays are extremely memory-efficient and much faster than lists of Python objects.
Common Mistakes with __slots__
Here are the most frequent errors developers make when using __slots__:
1. Forgetting to include '__weakref__' and '__dict__': If you need weak references or an instance dictionary, include them explicitly in the slots.
class Flexible:
__slots__ = ('x', '__weakref__', '__dict__')
f = Flexible()
f.x = 1
f.y = 2 # OK, because dict is in slots
2. Using __slots__ with multiple inheritance: Multiple inheritance with __slots__ works only when all base classes define exactly the same set of slots. Otherwise, Python raises an error.
3. Attempting to serialize with pickle: Objects with __slots__ can be pickled, but you need to ensure the class is available at deserialization time. Classes relying on __slots__ generally work well with pickle, as long as you implement __getstate__ and __setstate__ if needed.
The Python glossary documentation provides precise definitions and links to additional resources on __slots__ and other language internals.
Advanced Tips
For developers who want to go further, here are some advanced techniques with __slots__:
Introspection with dir()
Even without __dict__, you can still inspect objects with dir(). The function shows all available attributes, including those defined in __slots__.
Dynamic Slots
Can you modify __slots__ after class definition? Technically no, it is a class attribute set at compile time. However, you can use __dict__ as an additional slot to allow controlled dynamic assignment.
Comparison with Compiled Languages
The __slots__ mechanism resembles how languages like C++ or Java store object attributes: a contiguous block of memory with fixed offsets. This brings Python closer to lower-level language performance in specific scenarios, without losing the expressive syntax Python is known for.
Conclusion
__slots__ is a powerful tool in the arsenal of any Python developer who cares about performance and memory efficiency. When used correctly, it can reduce memory consumption by over 50% and significantly speed up attribute access. It is not a one-size-fits-all solution, but in the right scenarios, large-scale objects, games, data processing, it makes a transformative difference.
Start by analyzing your current projects: identify classes that generate many instances with fixed attributes and try adding __slots__. Measure memory usage before and after, and see the benefits for yourself. Investing time in understanding this internal Python mechanism will make you a more complete developer, ready for performance challenges.
To continue your studies, check out our complete guide on object-oriented programming in Python. [link_interno_1]
References and Resources