Using slots | Python
First, a small disclaimer.
This article is inspired by my learning. When I was just starting my Python-way, on one of the forums I saw a new concept for myself – slots. But no matter how much I searched, there were very few articles on the web on this topic, so it was quite difficult to understand and understand the slots. This article is intended to help beginners in this topic, but I’m sure even experienced developers will find something new here.
When we create objects for classes, memory is required and the attribute is stored as a dictionary (in dict). In case we need to allocate thousands of objects, it will take quite a lot of memory space.
Fortunately, there is a way out – slots, they provide a special mechanism for reducing the size of objects. This is the concept of memory optimization on objects. Also, the use slots allows us to speed up access to attributes.
An example of a python object without slots:
class NoSlots:
def __init__(self):
self.a = 1
self.b = 2
if __name__ == "__main__":
ns = NoSlots()
print(ns.dict)
Exit:
{'a': 1, 'b': 2}
Because every object in Python contains a dynamic dictionary which allows you to add attributes. For each instance object, we will have a dictionary instance which consumes more space and wastes a lot of RAM. Python does not have a default function to allocate a static amount of memory when creating an object to store all of its attributes.
Usage slots reduces wasted space and speeds up the program by allocating space for a fixed number of attributes.
An example of a python object with slots:
class WithSlots(object):
slots = ['a', 'b']
def __init__(self):
self.a = 1
self.b = 2
if __name__ == "__main__":
ws = WithSlots()
print(ws.slots)
Exit:
['a', 'b']
python example if we use dict:
class WithSlots:
slots=['a', 'b']
def init(self):
self.a = 1
self.b = 2
if __name__ == "__main__":
ws = WithSlots()
print(ws.dict)
Exit:
AttributeError: объект WithSlots не имеет атрибута 'dict'
As we can see, an AttributeError will be raised. It’s not hard to guess that since we can’t call dictthen we will not be able to create new attributes.
This is about the memory consumed, and let’s look at the speed of access to attributes:
Let’s write a little test:
class Foo(object):
slots = ('foo',)
class Bar(object):
pass
def get_set_delete(obj):
obj.foo = 'foo'
obj.foo
del obj.foo
def test_foo():
get_set_delete(Foo())
def test_bar():
get_set_delete(Bar())
And using the module timeit Let’s estimate the execution time:
>>> import timeit
>>> min(timeit.repeat(test_foo))
0.2567792439949699
>>> min(timeit.repeat(test_bar))
0.34515008199377917
So it turns out that a class using slots about 25-30% faster on attribute access operations. Of course, this figure may vary depending on the version of the language or OS on which the program is launched.
As we can see, using slots is quite simple, but there are some pitfalls. For example, inheritance. It must be understood that the meaning slots inherited, however this does not prevent the creation dict.
Thus, child classes will not prohibit adding dynamic attributes, and they will be added to __dict__, with all the ensuing costs (memory and performance).
class SlotsClass:
slots = ('foo', 'bar')
class ChildSlotsClass(SlotsClass):
pass
>>> obj = ChildSlotsClass()
>>> obj.slots
('foo', 'bar')
>>> obj.foo = 5
>>> obj.something_new = 3
>>> obj.dict
{'something_new': 3}
If we need the child class to also be limited by slots, we will have to assign a value to the attribute in it as well slots. By the way, there is no need to duplicate the slots already specified in the parent class.
class SlotsClass:
slots = ('foo', 'bar')
class ChildSlotsClass(SlotsClass):
slots = ('baz',)
>>> obj = ChildSlotsClass()
>>> obj.foo = 5py
>>> obj.baz = 6
>>> obj.something_new = 3
Traceback (most recent call last):
File "python", line 12, in <module>
AttributeError: 'ChildSlotsClass' object has no attribute 'something_new'
Much worse is the case with multiple inheritance. If we have two parent classes, each of which has slots defined, then the attempt to create a child class is doomed to failure.
class BaseOne:
slots = ('param1',)
class BaseTwo:
slots = ('param2',)
>>> class Child(BaseOne, BaseTwo): slots = ()
Exit:
Traceback (most recent call last):
File "<pyshell#68>", line 1, in <module>
class Child(BaseA, BaseB): slots = ()
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
One way to solve this problem is abstract classes. But I think we’ll talk about it next time.
And finally, the important conclusions:
Without dictionary variable
dict
instances cannot be assigned attributes not specified in the definitionslots
. If you try to name a variable that is not in the list, you will get an errorAttributeError
. If dynamic assignment of new variables is required, add the value'dict'
in attribute declarationslots
.Attributes
slots
declared in parent classes are available in child classes. However, child subclasses will getdict
if they don’t overrideslots
.If a class defines a slot also defined in the base class, the instance variable defined by the base class slot is not available. This leads to ambiguous program behavior.
Attribute
slots
does not work for classes inherited from built-in types variable lengthsuch asint
,bytes
andtuple
.Attribute
slots
any non-string iterable can be assigned. Dictionaries may be used, and the values corresponding to each key may be assigned a special meaning.Purpose
class
works if both classes have the sameslots
.Multiple inheritance can be used with multiple slotted parent classes, but only one parent is allowed to have attributes created with slots (other classes must have empty slots mocks), violation will throw an exception
TypeError
.
I hope everything was simple and clear, and now you will begin to use more often slots in your projects.
I look forward to your opinion on this topic, good luck to everyone!
My GitHub: https://github.com/Ryize