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_name
if __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_name
if __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: str
if __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 : str
def get_set_delete(person: Person | PersonSlot):
person.first_name = "Raphael"
_ = person.first_name
del person.first_name
def 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-05
print(f"Slots: {slots}") Slots: 1.8151000404031947e-05
print(f"Percentage of performance: {(slots / no_slots) * 100}") Percentage of performance: 53.520670180175635
if __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!!!!