One of Python's strengths is its flexibility. Object attributes can be added dynamically at runtime, making classes easy to extend. However, this flexibility comes with additional memory overhead.
If your application creates thousands or even millions of objects, this overhead can become significant. Python provides a feature called __slots__ that helps reduce memory consumption by preventing the creation of an instance dictionary.
Let's see how it works.
Objects Without __slots__
class UserProfile:
# without __slots__
def __init__(self, name, email, mobile, age):
self.name = name
self.email = email
self.mobile = mobile
self.age = age
user_object = UserProfile("Ganesh", "ganesha@example.com", 12345, 25)The attributes are stored inside an internal dictionary (__dict__).
>>> print(user_object.__dict__)
{'name': 'Ganesh',
'email': 'ganesha@example.com',
'mobile': 12345,
'age': 25}You can also inspect the available attributes and methods:
>>> print(dir(user_object))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
'__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__',
'__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__',
'__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__static_attributes__', '__str__', '__subclasshook__',
'__weakref__', 'age', 'email', 'mobile', 'name']Notice that __dict__ appears in the list.
Measuring Memory Usage
sys.getsizeof() only reports the size of the object container itself and not the complete memory occupied by nested objects. To calculate the actual memory footprint recursively, we can use the pympler package.
from pympler import asizeof
>>> print(f"Memory Taken: {asizeof.asizeof(user_object)} bytes")
Memory Taken: 712 bytesThe total memory taken by this object is 712 bytes.
Objects With __slots__
Now let's define the same class using __slots__.
class UserProfile:
# with __slots__
__slots__ = ('name', 'email', 'mobile', 'age')
def __init__(self, name, email, mobile, age):
self.name = name
self.email = email
self.mobile = mobile
self.age = age
user_object = UserProfile("Ganesh", "ganesha@example.com", 12345, 25)If you inspect the object:
>>> print(dir(user_object))
['__class__', '__delattr__', '__dir__', '__doc__',
'__eq__', '__firstlineno__', '__format__', '__ge__',
'__getattribute__', '__getstate__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__le__', '__lt__',
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__slots__',
'__static_attributes__', '__str__', '__subclasshook__',
'age', 'email', 'mobile', 'name']You'll notice that __dict__ is no longer present.
Trying to access it raises an error:
>>> print(user_object.__dict__)
Traceback (most recent call last):
File "<python-input-20>", line 1, in <module>
print(user_object.__dict__)
^^^^^^^^^^^^^^^^^^^
AttributeError: 'UserProfile' object has no attribute '__dict__'. Did you mean: '__dir__'?Memory Usage with __slots__
>>> print(f"Memory Taken: {asizeof.asizeof(user_object)} bytes")
Memory Taken: 240 bytesIn this example, memory usage dropped from 712 bytes to 240 bytes, which is almost a 66% reduction.
Note: Exact memory numbers may vary depending on your Python version, operating system, and architecture.
Dynamic Attributes Are Not Allowed
Without __slots__, you can add attributes at runtime:
>>> user_object.country = "IN"
>>> user_object.country
'IN'With __slots__, this raises:
>>> user_object.country = "IN"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
user_object.country = "IN"
^^^^^^^^^^^^^^^^^^
AttributeError: 'UserProfile' object has no attribute 'country' and no __dict__ for setting new attributesThis happens because __slots__ removes the per-instance __dict__ and restricts objects to a predefined set of attributes.
Advantages of Using __slots__
- Reduced Memory Usage — Objects no longer maintain an instance dictionary, saving memory when many objects are created.
- Better Performance — Attribute access may be slightly faster, although the primary benefit of
__slots__is reduced memory usage. - Prevents Accidental Attribute Creation — Typos such as
user_object.emal = "abc@example.com"will immediately raise an error instead of silently creating a new attribute. - Useful for Large Data Models — Classes that represent millions of records, cache entries, or data objects can benefit significantly from lower memory consumption.
Disadvantages of Using __slots__
- No Dynamic Attributes — You cannot add new attributes unless they are explicitly declared in
__slots__. - No
__dict__— Some libraries and debugging tools rely on__dict__, which may cause compatibility issues. - Inheritance Becomes More Complex — Subclasses must define their own
__slots__, and mixing slotted and non-slotted classes requires extra care. - Reduced Flexibility — If the class structure changes frequently, maintaining
__slots__can become inconvenient.
When Should You Use __slots__?
Use __slots__ when:
- You create a large number of objects.
- The set of attributes is fixed.
- Memory usage is important.
- You want to prevent accidental attribute creation.
Avoid using __slots__ when:
- You need dynamic attributes.
- Flexibility is more important than memory optimization.
- The class is small and only a few instances are created.
Conclusion
__slots__ is a simple but powerful optimization in Python. By removing the per-instance dictionary, it can significantly reduce memory usage and slightly improve attribute access speed.
__slots__ is most beneficial when creating a large number of objects with a fixed set of attributes. Although it reduces flexibility, the memory savings can be substantial in memory-sensitive applications.