Speed Up Your Python classes with slot
The special attribute __slots__
allows you to explicitly state which instance attributes you expect your object instances to have, with the expected results:
- faster attribute access.
- space savings in memory.
The space savings is from
- Storing value references in slots instead of
__dict__
. - Denying
__dict__
and__weakref__
creation if parent classes deny them and you declare__slots__
.
When we create an object from a class, the attributes of the object will be stored in a dictionary called __dict__
. We use this dictionary to get and set attributes. It allows us to dynamically create new attributes after the creation of the object.
Let’s create a simple class Person
that initially has 2 attributes first_name
and last_name
. If we print out __dict__
of the object, we will get the key and value of each attribute. Meanwhile, we also print __dict__
of the class which will be needed later. After that, a new attribute reviewer
is added to the object, and we can see it in the updated __dict__
.
class Person:
def __init__(self, first_name: str, last_name: str):
self.first_name = first_name
self.last_name = last_nameif __name__ == "__main__":
person = Person("Chidozie", "Okafor")
print(person.__dict__)
{'first_name': 'Chidozie', 'last_name': 'Okafor'}
print(Person.__dict__)
{'__module__': '__main__', '__doc__': None, '__init__': <function __init__ at 0x10e9cd140>}
The issues with dictionary is memory consumption and also access dictionary involves hashing, Dictionary is in fact a hash map. The worst case of the time complexity of get/set in a hash map is O(n).
slots provide a special mechanism to reduce the size of objects.It is a concept of memory optimisation on objects.
As every object in Python contains a dynamic dictionary that allows adding attributes. For every instance object, we will have an instance of a dictionary that consumes more space and wastes a lot of RAM. In Python, there is no default functionality to allocate a static amount of memory while creating the object to store all its attributes.
Usage of __slots__ reduce the wastage of space and speed up the program by allocating space for a fixed amount of attribute
To create a slot, all we need to do is to add __slots__ field or slots=True if we are using dataclass.
class PersonSlot:
__slots__ = ["first_name", "last_name"]
def __init__(self, first_name: str, last_name: str):
self.first_name = first_name
self.last_name = last_nameif __name__ == "__main__":
person = Person("Chidozie", "Okafor")
print(person.__dict__)
{'first_name': 'Chidozie', 'last_name': 'Okafor'}
print(Person.__dict__)
{'__module__': '__main__', '__slots__': ['first_name', 'last_name'], '__init__': <function __init__ at 0x10432b140>, '__doc__': None}
To avoid repetition of variable names, we can use dataclasses to create a class that will automatically create slots for us.
from dataclasses import dataclass@dataclass(slots=True)
class PersonSlot:
first_name: str
last_name: strif __name__ == "__main__":
person = PersonSlot("Chidozie", "Okafor")
print(person.__dict__)
print(PersonSlot.__dict__)
Advantages of using slots:
- Slots are faster than __dict__.
- Slots are more memory efficient.
- Slots are more secure.
- Slots are more convenient.
- Slots are more readable.
- Slots are more efficient.
Disadvantages of using slots:
- Slots are not compatible with Multiple inheritance and Mixins.
- Slots are not compatible with metaclasses.
- Slots are not compatible with ABCs.
- Slots are not compatible with pickling.
- Slots are not compatible with copy.
- Slots are not compatible with deepcopy.
Let’s see how fast slots are compare to normal dictionary.
import timeit
from dataclasses import dataclass
from functools import partial@dataclass(slots=False)
class Person:
first_name : str
last_name: str@dataclass(slots=True)
class PersonSlot:
first_name : str
last_name : strdef get_set_delete(person: Person | PersonSlot):
person.first_name = "Raphael"
_ = person.first_name
del person.first_namedef get_percentage_of_performance():
person = Person("Chidozie", "Okafor")
person_slot = PersonSlot("Chidozie", "Okafor")
no_slots = min(timeit.repeat(partial(get_set_delete, person), number=100, repeat=3))
slots = min(timeit.repeat(partial(get_set_delete, person_slot), number=100, repeat=3))
print(f"No Slots: {no_slots}") No Slots: 3.3914000596269034e-05print(f"Slots: {slots}") Slots: 1.8151000404031947e-05
print(f"Percentage of performance: {(slots / no_slots) * 100}") Percentage of performance: 53.520670180175635if __name__ == "__main__":
get_percentage_of_performance()
There are so many things we can do with slots to help improve the speed of our code. try as many as possible and share with others.
Happy coding!!!!