Как __slots__ избегает поиска в словаре?

Я слышал, что __slots__ делает объекты быстрее, избегая поиска в словаре. Моя путаница исходит из того, что Python является динамичным языком. На статическом языке мы избегаем поиска словаря для a.test, выполняя оптимизацию времени компиляции, чтобы сохранить индекс в инструкции, которую мы запускаем.

Теперь, в Python, a также может быть легко другим объектом, который имеет словарь или другой набор атрибутов. Похоже, нам все равно придется искать словарный словарь - единственное отличие состоит в том, что нам нужен только один словарь для класса, а не словарь для каждого объекта.

С этим рациональным,

  • Как __slots__ избежать поиска в словаре?
  • Доступно ли слотам быстрее доступ к объектам?

Ответ 1

__slots__ не ускоряет доступ к атрибутам (значительно):

>>> 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

Целью использования __slots__ является сохранение памяти; вместо использования отображения .__dict__ в экземпляре класс объекты дескрипторов для каждого атрибута, названного в __slots__, и экземпляры имеют атрибут, присвоенный им или нет, имеет фактическое значение:

>>> 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'>

Таким образом, python все равно должен смотреть на класс для каждого доступа к атрибуту в экземпляре Foo (чтобы найти дескриптор). Любой неизвестный атрибут (скажем, Foo.ham) по-прежнему будет приводить к тому, что Python просматривает класс MRO для поиска этого атрибута и включает поиск словаря. И вы все равно можете назначить дополнительные атрибуты классу:

>>> 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'

Дескрипторы слота создаются при создании класса и получают доступ к памяти, назначенной каждому экземпляру, для хранения и получения ссылки на соответствующее значение (тот же кусок памяти, который отслеживает количество ссылок экземпляров и ссылку на объект класса). Без слотов дескриптор для __dict__ используется для доступа к ссылке на объект dict таким же образом.

Ответ 2

Это может ускорить работу программы, в которой вы создаете множество объектов одного класса, поистине никогда не изменяя, какие у них есть атрибуты, а промахи в кешках на всех этих дублирующих словарях представляют собой настоящую проблему с производительностью.

Это действительно просто частный случай общей ситуации, когда экономия пространства иногда экономит время, а кеш является ограничивающим фактором.

Таким образом, он, вероятно, не ускорит доступ к одному объекту, но может ускорить доступ ко многим объектам того же типа.

См. также этот вопрос.