__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.
__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.
python - Usage of __slots__? - Stack Overflow
What is the purpose of __slots__ in Python?
When should I use __slots__ in a Python class, and what are the tradeoffs?
Slots for class objects? (i.e. avoiding creating __dict__ for classes themselves)
Videos
TLDR
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__. This has the effect of denying the creation of non-slotted attributes on its instances, including within the class body (such as in methods like__init__).
Quick Caveats
Small caveat, you should only declare a particular slot one time in an inheritance tree. For example:
Copyclass Base:
__slots__ = 'foo', 'bar'
class Right(Base):
__slots__ = 'baz',
class Wrong(Base):
__slots__ = 'foo', 'bar', 'baz' # redundant foo and bar
Python doesn't object when you get this wrong (it probably should), and problems might not otherwise manifest, but your objects will take up more space than they should. Python 3.8:
Copy>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
This is because Base's slot descriptor has a slot separate from Wrong's. This shouldn't usually come up, but it could:
Copy>>> w = Wrong()
>>> w.foo = 'foo'
>>> Base.foo.__get__(w)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: foo
>>> Wrong.foo.__get__(w)
'foo'
The biggest caveat is for multiple inheritance - multiple "parent classes with nonempty slots" cannot be combined.
To accommodate this restriction, follow best practices: create abstractions with empty __slots__ for every parent class (or for every parent class but one), then inherit from these abstractions instead of their concrete versions in your new concrete class. (The original parent classes should also inherit from their respective abstractions, of course.)
See section on multiple inheritance below for an example.
Requirements
To have attributes named in
__slots__to actually be stored in slots instead of a__dict__, a class must inherit fromobject(automatic in Python 3, but must be explicit in Python 2).To prevent the creation of a
__dict__, you must inherit fromobjectand all classes in the inheritance must declare__slots__and none of them can have a'__dict__'entry.
There are a lot of details if you wish to keep reading.
Why use __slots__
Faster attribute access
The creator of Python, Guido van Rossum, states that he actually created __slots__ for faster attribute access.
It's trivial to demonstrate measurably significant speedup:
Copyimport timeit
class Foo(object): __slots__ = 'foo',
class Bar(object): pass
slotted = Foo()
not_slotted = Bar()
def get_set_delete_fn(obj):
def get_set_delete():
obj.foo = 'foo'
obj.foo
del obj.foo
return get_set_delete
and
Copy>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
The slotted access is almost 30% faster in Python 3.5 on Ubuntu.
Copy>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
In Python 2 on Windows I have measured it about 15% faster.
Memory Savings
Another purpose of __slots__ is to reduce the space in memory that each object instance takes up.
My own contribution to the documentation clearly states the reasons behind this:
The space saved over using
__dict__can be significant.
SQLAlchemy attributes a lot of memory savings to __slots__.
To verify this, using the Anaconda distribution of Python 2.7 on Ubuntu Linux, with guppy.hpy (aka heapy) and sys.getsizeof, the size of a class instance without __slots__ declared, and nothing else, is 64 bytes. That does not include the __dict__. Thank you Python for lazy evaluation again, the __dict__ is apparently not called into existence until it is referenced, but classes without data are usually useless. When called into existence, the __dict__ attribute is a minimum of 280 bytes additionally.
In contrast, a class instance with __slots__ declared to be () (no data) is only 16 bytes, and 56 total bytes with one item in slots, 64 with two.
For 64 bit Python, I illustrate the memory consumption in bytes in Python 2.7 and 3.6, for __slots__ and __dict__ (no slots defined) for each point where the dict grows in 3.6 (except for 0, 1, and 2 attributes):
Copy Python 2.7 Python 3.6
attrs __slots__ __dict__* __slots__ __dict__* | *(no slots defined)
none 16 56 + 272† 16 56 + 112† | †if __dict__ referenced
one 48 56 + 272 48 56 + 112
two 56 56 + 272 56 56 + 112
six 88 56 + 1040 88 56 + 152
11 128 56 + 1040 128 56 + 240
22 216 56 + 3344 216 56 + 408
43 384 56 + 3344 384 56 + 752
So, in spite of smaller dicts in Python 3, we see how nicely __slots__ scales for instances to save us memory, and that is a major reason you would want to use __slots__.
Just for completeness of my notes, note that there is a one-time cost per slot in the class's namespace of 64 bytes in Python 2, and 72 bytes in Python 3, because slots use data descriptors like properties, called "members".
Copy>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
Demonstration
To deny the creation of a __dict__, you must subclass object. Everything subclasses object in Python 3, but in Python 2 you had to be explicit:
Copyclass Base(object):
__slots__ = ()
now:
Copy>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
File "<pyshell#38>", line 1, in <module>
b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'
Or subclass another class that defines __slots__
Copyclass Child(Base):
__slots__ = ('a',)
and now:
Copyc = Child()
c.a = 'a'
but:
Copy>>> c.b = 'b'
Traceback (most recent call last):
File "<pyshell#42>", line 1, in <module>
c.b = 'b'
AttributeError: 'Child' object has no attribute 'b'
To allow __dict__ creation while subclassing slotted objects, just add '__dict__' to the __slots__ (note that slots are ordered, and you shouldn't repeat slots that are already in parent classes):
Copyclass SlottedWithDict(Child):
__slots__ = ('__dict__', 'b')
swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'
and
Copy>>> swd.__dict__
{'c': 'c'}
Or you don't even need to declare __slots__ in your subclass, and you will still use slots from the parents, but not restrict the creation of a __dict__:
Copyclass NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'
and:
Copy>>> ns.__dict__
{'b': 'b'}
However, __slots__ may cause problems for multiple inheritance:
Copyclass BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
```python
Because creating a child class from parents with both non-empty slots fails:
```python
>>> class Child(BaseA, BaseB): __slots__ = ()
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
If you run into this problem, You could just remove __slots__ from the parents, or if you have control of the parents, give them empty slots, or refactor to abstractions:
Copyfrom abc import ABC
class AbstractA(ABC):
__slots__ = ()
class BaseA(AbstractA):
__slots__ = ('a',)
class AbstractB(ABC):
__slots__ = ()
class BaseB(AbstractB):
__slots__ = ('b',)
class Child(AbstractA, AbstractB):
__slots__ = ('a', 'b')
c = Child() # no problem!
Add '__dict__' to __slots__ to get dynamic assignment
Copyclass Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
and now:
Quoting Jacob Hallen:
The proper use of
__slots__is to save space in objects. Instead of having a dynamic dict that allows adding attributes to objects at anytime, there is a static structure which does not allow additions after creation. [This use of__slots__eliminates the overhead of one dict for every object.] While this is sometimes a useful optimization, it would be completely unnecessary if the Python interpreter was dynamic enough so that it would only require the dict when there actually were additions to the object.Unfortunately there is a side effect to slots. They change the behavior of the objects that have slots in a way that can be abused by control freaks and static typing weenies. This is bad, because the control freaks should be abusing the metaclasses and the static typing weenies should be abusing decorators, since in Python, there should be only one obvious way of doing something.
Making CPython smart enough to handle saving space without
__slots__is a major undertaking, which is probably why it is not on the list of changes for P3k (yet).