I've heard that __slots__
makes objects faster by avoiding a dictionary lookup. My confusion comes from Python being a dynamic language. In a static language, we avoid a dictionary lookup for a.test
by doing a compile-time optimisation to save the index in the instruction we run.
Now, in Python, a
could just as easily be another object that has a dictionary or a different set of attributes. It seems like we'll still have to do a dictionary lookup - the only difference seems to be that we only need one dictionary for the class, rather than a dictionary for each object.
With this rational,
__slots__
avoid a dictionary lookup?Python | Use of __slots__ 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.
The __dict__ in Python represents a dictionary or any mapping object that is used to store the attributes of the object. They are also known as mappingproxy objects. To put it simply, every object in Python has an attribute that is denoted by __dict__.
__slots__
does not (significantly) speed up attribute access:
>>> class Foo(object):
... __slots__ = ('spam',)
... def __init__(self):
... self.spam = 'eggs'
...
>>> class Bar(object):
... def __init__(self):
... self.spam = 'eggs'
...
>>> import timeit
>>> timeit.timeit('t.spam', 'from __main__ import Foo; t=Foo()')
0.07030296325683594
>>> timeit.timeit('t.spam', 'from __main__ import Bar; t=Bar()')
0.07646608352661133
The goal of using __slots__
is to save memory; instead of using a .__dict__
mapping on the instance, the class has descriptors objects for each and every attribute named in __slots__
and instances have the attribute assigned wether or not they have an actual value:
>>> class Foo(object):
... __slots__ = ('spam',)
...
>>> dir(Foo())
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'spam']
>>> Foo().spam
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: spam
>>> Foo.spam
<member 'spam' of 'Foo' objects>
>>> type(Foo.spam)
<type 'member_descriptor'>
So python still has to look at the class for each attribute access on an instance of Foo
(to find the descriptor). Any unknown attribute (say, Foo.ham
) will still result in Python looking through the class MRO to search for that attribute, and that includes dictionary searches. And you can still assign additional attributes to the class:
>>> Foo.ham = 'eggs'
>>> dir(Foo)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'ham', 'spam']
>>> Foo().ham
'eggs'
The slot descriptors are created when the class is created, and access memory assigned to each instance to store and retrieve a reference to the associated value (the same chunk of memory that tracks instance reference counts and a reference back to the class object). Without slots, a descriptor for __dict__
is used accessing a reference to a dict
object in the same manner.
It might speed up a program where you instantiate lots of objects of the same class, genuinely never change what attributes they have, and cache misses on all those duplicate dictionaries present a real performance problem.
This is really just a special case of the general situation where saving space sometimes saves time as well, where cache is the limiting factor.
So, it probably won't make accessing one object faster, but may speed up accessing many objects of the same type.
See also this question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With