Python - Python __slots__ and Memory-Efficient Classes
Introduction
In Python, classes normally allow each object to have its own __dict__, which is a dictionary used to store the object's attributes. This provides flexibility because new attributes can usually be added to an object at any time. However, when an application creates a very large number of objects, maintaining a separate dictionary for every object can consume a significant amount of memory.
Python provides __slots__ as a mechanism for reducing the memory overhead associated with instances. By defining __slots__ inside a class, we can explicitly specify which instance attributes the objects of that class are allowed to have. In many cases, this eliminates the need for a per-instance __dict__ and can make objects more memory-efficient.
What Is __slots__?
__slots__ is a special class attribute that contains a sequence of attribute names that instances of the class are permitted to use.
For example:
class Student:
__slots__ = ("name", "age")
def __init__(self, name, age):
self.name = name
self.age = age
Here, the Student class declares two permitted instance attributes: name and age.
Objects can be created normally:
student = Student("Rahul", 20)
print(student.name)
print(student.age)
Output:
Rahul
20
However, an attribute that is not included in __slots__ cannot normally be added:
student.city = "Bangalore"
This results in an AttributeError because city was not declared in __slots__.
Why Does Python Use __dict__?
To understand the purpose of __slots__, it is useful to understand how ordinary Python objects store attributes.
Consider:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
When an object is created:
student = Student("Rahul", 20)
Python normally maintains the object's attributes in a dictionary that can be accessed through:
print(student.__dict__)
Output:
{'name': 'Rahul', 'age': 20}
The dictionary provides considerable flexibility. For example:
student.city = "Bangalore"
student.course = "Python"
Now:
print(student.__dict__)
might produce:
{'name': 'Rahul', 'age': 20, 'city': 'Bangalore', 'course': 'Python'}
This flexibility is useful, but the dictionary itself requires memory. If an application creates thousands or millions of small objects, the memory overhead can become significant.
How __slots__ Reduces Memory Usage
When a class uses __slots__, Python can store the declared attributes more efficiently rather than maintaining a normal instance dictionary for each object.
For example:
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
A large number of Point objects can be created:
points = []
for i in range(100000):
points.append(Point(i, i * 2))
Because Point uses __slots__, each instance does not need the usual per-instance attribute dictionary, reducing memory overhead.
The exact amount of memory saved depends on the Python implementation, object structure, number of attributes, and other factors. Therefore, __slots__ should not be considered a guarantee of a specific memory reduction.
Preventing Dynamic Attributes
One important characteristic of __slots__ is that it restricts the attributes that can normally be assigned to an object.
Consider:
class Employee:
__slots__ = ("name", "salary")
def __init__(self, name, salary):
self.name = name
self.salary = salary
This works:
employee = Employee("Anita", 50000)
employee.name = "Priya"
employee.salary = 60000
But this does not:
employee.department = "IT"
Python raises an error similar to:
AttributeError: 'Employee' object has no attribute 'department'
This can be useful when a class is designed to have a fixed set of attributes.
Accessing __dict__ with __slots__
A class that defines __slots__ generally does not provide an instance __dict__.
For example:
class Product:
__slots__ = ("name", "price")
def __init__(self, name, price):
self.name = name
self.price = price
The following may fail:
product = Product("Laptop", 50000)
print(product.__dict__)
The reason is that the object does not normally have an instance dictionary.
If dynamic attributes are actually required, __dict__ can explicitly be included:
class Product:
__slots__ = ("name", "price", "__dict__")
def __init__(self, name, price):
self.name = name
self.price = price
Now additional attributes can be assigned:
product = Product("Laptop", 50000)
product.brand = "Example"
print(product.__dict__)
However, adding __dict__ removes much of the memory-saving advantage for those dynamic attributes, so it should be used only when that flexibility is necessary.
__slots__ and Class Attributes
__slots__ defines permitted instance attributes, not ordinary class attributes.
For example:
class Student:
school = "ABC School"
__slots__ = ("name", "age")
def __init__(self, name, age):
self.name = name
self.age = age
Here, school is a class attribute shared by the class's instances, while name and age are declared instance attributes.
You can access:
student = Student("Rahul", 20)
print(student.name)
print(student.age)
print(student.school)
Output:
Rahul
20
ABC School
The class attribute school is not an instance slot.
__slots__ Does Not Make Objects Immutable
A common misunderstanding is that __slots__ makes an object immutable. It does not.
For example:
class Student:
__slots__ = ("name", "age")
def __init__(self, name, age):
self.name = name
self.age = age
The values can still be changed:
student = Student("Rahul", 20)
student.name = "Amit"
student.age = 21
The object remains mutable.
__slots__ primarily restricts which attributes can be stored and can reduce instance memory overhead. It does not prevent existing attributes from being modified.
__slots__ with Inheritance
Inheritance introduces additional considerations.
For example:
class Person:
__slots__ = ("name",)
def __init__(self, name):
self.name = name
class Student(Person):
__slots__ = ("course",)
def __init__(self, name, course):
super().__init__(name)
self.course = course
A Student object can contain the attributes declared in both classes:
student = Student("Rahul", "Python")
print(student.name)
print(student.course)
Output:
Rahul
Python
A subclass should generally define its own __slots__ when the intention is to continue restricting instance attributes.
If a subclass does not define __slots__, it can have an instance dictionary, which can reduce or eliminate the memory advantages expected from slots.
Duplicate Slot Names
When using inheritance, care should be taken not to unnecessarily repeat slot names.
For example:
class Person:
__slots__ = ("name",)
class Student(Person):
__slots__ = ("name", "course")
The repeated name creates a separate slot in the subclass and can lead to confusing behavior and unnecessary storage. Slot names should therefore generally be unique across the inheritance hierarchy unless there is a specific reason for the duplication.
__slots__ and Weak References
Some Python features require an object to support weak references. Classes using __slots__ may need to explicitly include __weakref__ if weak references are required.
For example:
class Employee:
__slots__ = ("name", "__weakref__")
def __init__(self, name):
self.name = name
This allows instances to participate in Python's weak-reference mechanism.
This is an advanced consideration and is mainly relevant when working with caching systems, object lifecycle management, or libraries that use weak references.
Advantages of __slots__
The major advantages include:
1. Reduced memory overhead
For classes containing many small objects, avoiding a separate instance dictionary can significantly reduce memory consumption.
2. Faster attribute access in some situations
Because attributes can be managed differently from ordinary dictionary-based instance attributes, some workloads may see performance benefits. However, performance improvements should be measured rather than assumed.
3. Attribute restriction
Only the attributes declared in __slots__ can normally be assigned, which can help prevent accidental creation of unexpected attributes.
4. Clearer object structure
The class explicitly declares the instance attributes that it expects to use. This can make the intended structure of lightweight objects easier to understand.
Disadvantages of __slots__
Despite its benefits, __slots__ is not appropriate for every class.
1. Reduced flexibility
You cannot normally add arbitrary attributes to instances.
student.city = "Bangalore"
will fail if city is not included in the slots.
2. Inheritance can become more complicated
When multiple classes use inheritance and slots, developers need to understand how slots interact between parent and child classes.
3. Some libraries expect __dict__
Certain libraries, frameworks, or serialization approaches may rely on an object's __dict__. Using __slots__ can therefore require additional consideration.
4. It adds design constraints
If the class may need new instance attributes in the future, using __slots__ can make future modifications less convenient.
5. Memory savings are workload-dependent
For a small number of objects, the memory savings may be insignificant compared with the added restrictions. The main benefit tends to appear when many instances of relatively small classes are created.
Example: Regular Class vs __slots__
Regular class:
class Employee:
def __init__(self, name, department):
self.name = name
self.department = department
Slots-based class:
class Employee:
__slots__ = ("name", "department")
def __init__(self, name, department):
self.name = name
self.department = department
Both classes can be used similarly:
employee = Employee("Anita", "IT")
print(employee.name)
print(employee.department)
The major difference is how instance attributes are represented internally and whether arbitrary additional attributes can be added.
When Should You Use __slots__?
__slots__ is particularly useful when:
-
A program creates a very large number of instances.
-
Each instance contains a small, fixed set of attributes.
-
Memory efficiency is important.
-
The class does not need arbitrary dynamic attributes.
-
The object structure is stable and well-defined.
For example, applications working with large collections of coordinates, records, graph nodes, tokens, or other lightweight objects may benefit from slots.
For ordinary application classes with relatively few instances, the simplicity and flexibility of a normal class may be more valuable than the potential memory savings.
Conclusion
Python's __slots__ provides a way to create more memory-efficient classes by declaring a fixed set of permitted instance attributes. It can avoid the normal per-instance __dict__, reduce memory overhead, and prevent accidental addition of undeclared attributes.
However, __slots__ is a design choice rather than a general replacement for normal Python classes. It introduces restrictions around dynamic attributes, inheritance, weak references, and some library interactions. Therefore, it is most useful for well-defined, lightweight objects that are created in large numbers and where memory usage is an important consideration.