Python __slots__
Summary: in this tutorial, you will learn about the Python __slots__
and how how to use it to make your class more efficient.
Introduction to the Python __slots__
The following defines a Point2D
class that has two attributes including x
and y
coordinates:
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y def __repr__(self):
return f'Point2D({self.x},{self.y})'
Code language: Python (python)
Each instance of the Point2D
class has its own __dict__
attribute that stores the instance attributes. For example:
point = Point2D(0, 0)
print(point.__dict__)
Code language: Python (python)
By default, Python uses dictionaries to manage the instance attributes. The dictionary allows you to add more attributes to the instance dynamically at runtime. However, it also has a certain memory overhead. If the Point2D
class has many objects, there will be a lot of memory overhead.
To avoid the memory overhead, Python introduced the slots. If a class only contains fixed (or predetermined) instance attributes, you can use the slots to instruct Python to use a more compact data structure instead of dictionaries.
For example, if the Point2D
class has only two instance attributes, you can specify the attributes in the slots like this:
class Point2D:
__slots__ = ('x', 'y') def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Point2D({self.x},{self.y})'
Code language: Python (python)
In this example, you assign an iterable (a tuple) that contains the attribute names that you’ll use in the class.
By doing this, Python will not use the __dict__
for the instances of the class. The following will cause an AttributeError
error:
point = Point2D(0, 0)
print(point.__dict__)
Code language: Python (python)
Error:
AttributeError: 'Point2D' object has no attribute __dict__
Code language: Python (python)
Instead, you’ll see the __slots__
in the instance of the class. For example:
point = Point2D(0, 0)
print(point.__slots__)
Code language: Python (python)
Output:
('x', 'y')
Code language: Python (python)
Also, you cannot add more attributes to the instance dynamically at runtime. The following will result in an error:
point.z = 0
Code language: Python (python)
Error:
AttributeError: 'Point2D' object has no attribute 'z'
Code language: Python (python)
However, you can add the class attributes to the class:
Point2D.color = 'black'
pprint(Point2D.__dict__)
Code language: Python (python)
Output:
mappingproxy({'__doc__': None,
'__init__': <function Point2D.__init__ at 0x000001BBBA841310>,
'__module__': '__main__',
'__repr__': <function Point2D.__repr__ at 0x000001BBBA8413A0>,
'__slots__': ('x', 'y'),
'color': 'black',
'x': <member 'x' of 'Point2D' objects>,
'y': <member 'y' of 'Point2D' objects>})
Code language: Python (python)
This code works because Python applies the slots to the instances of the class, not the class.
Python __slots__ and single inheritance
Let’s examine the slots in the context of inheritance.
The base class uses the slots but the subclass doesn’t
The following defines the Point2D
as the base class and Point3D
as a subclass that inherits from the Point2D
class:
class Point2D:
__slots__ = ('x', 'y') def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Point2D({self.x},{self.y})'
class Point3D(Point2D):
def __init__(self, x, y, z):
super().__init__(x, y)
self.z = z
if __name__ == '__main__':
point = Point3D(10, 20, 30)
print(point.__dict__)
Code language: Python (python)
Output:
{'z': 30}
Code language: Python (python)
The Point3D
class doesn’t have slots so its instance has the __dict__
attribute. In this case, the subclass Point3D
uses slots from its base class (if available) and uses an instance dictionary.
If you want the Point3D
class to use slots, you can define additional attributes like this:
class Point3D(Point2D):
__slots__ = ('z',) def __init__(self, x, y, z):
super().__init__(x, y)
self.z = z
Code language: Python (python)
Note that you don’t specify the attributes that are already specified in the __slots__
of the base class.
Now, the Point3D
class will use slots for all attributes including x, y, and z.
The base class doesn’t use __slots__ and the subclass doesn’t
The following example defines a base class that doesn’t use the __slots__
and the subclass does:
class Shape:
passclass Point2D(Shape):
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
if __name__ == '__main__':
# use both slots and dict to store instance attributes
point = Point2D(10, 10)
print(point.__slots__)
print(point.__dict__)
# can add the attribute at runtime
point.color = 'black'
print(point.__dict__)
Code language: Python (python)
Output:
('x', 'y')
{'color': 'black'}
Code language: Python (python)
In this case, the instances of the Point2D
class uses both __slots__
and dictionary to store the instance attributes.
Summary
- Python uses dictionaries to store instance attributes of instances of a class. This allows you to dynamically add more attributes to instances at runtime but also create a memory overhead.
- Define
__slots__
in the class if it has predetermined instances attributes to instruct Python not to use dictionaries to store instance attributes. The__slots__
optimizes the memory if the class has many objects.