Какова цель __slots__
в Python - особенно когда я хочу его использовать, а когда нет?
В Python, какова цель
__slots__
и в каких случаях этого следует избегать?
Специальный атрибут __slots__
позволяет вам явно указать, какие атрибуты экземпляров ожидаются для ваших экземпляров объекта, с ожидаемыми результатами:
Экономия пространства от
__dict__
.__dict__
и __weakref__
создание, если родительские классы отрицают их, и вы объявляете __slots__
.Небольшое предостережение: вы должны объявить определенный слот только один раз в дереве наследования. Например:
class Base:
__slots__ = 'foo', 'bar'
class Right(Base):
__slots__ = 'baz',
class Wrong(Base):
__slots__ = 'foo', 'bar', 'baz' # redundant foo and bar
Python не возражает, когда вы ошибаетесь (возможно, так и должно быть), иначе проблемы могут не проявиться, но ваши объекты займут больше места, чем в противном случае. Python 3.8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
Это потому, что дескриптор слотов базы имеет слот, отдельный от неправильного. Это обычно не должно происходить, но это может:
>>> 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'
Наибольшее предостережение касается множественного наследования - несколько «родительских классов с непустыми слотами» не могут быть объединены.
Чтобы учесть это ограничение, следуйте рекомендациям: Вычтите все абстракции всех родителей, кроме одного или всех, от которых наследует их конкретный класс, соответственно, и ваш новый конкретный класс - предоставив абстракции (ям) пустые слоты (как абстрактные базовые классы в стандартная библиотека).
См. Раздел о множественном наследовании ниже для примера.
Для того чтобы имена атрибутов __slots__
были фактически сохранены в слотах вместо a __dict__
, класс должен наследоваться от object
.
Чтобы предотвратить создание __dict__
, вы должны наследовать, object
и все классы в наследовании должны объявить, __slots__
и ни один из них не может иметь '__dict__'
запись.
Есть много деталей, если вы хотите продолжить чтение.
__slots__
: более быстрый доступ к атрибутам.Создатель Python, Гвидо ван Россум, утверждает, что он действительно создан __slots__
для более быстрого доступа к атрибутам.
Тривиально продемонстрировать значительно более быстрый доступ:
import 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
а также
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
Слотный доступ почти на 30% быстрее в Python 3.5 на Ubuntu.
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
В Python 2 на Windows я измерил его примерно на 15% быстрее.
__slots__
: Экономия памятиДругая цель __slots__
состоит в том, чтобы уменьшить пространство в памяти, которое занимает каждый экземпляр объекта.
Мой собственный вклад в документацию ясно указывает причины этого :
Пространство, сэкономленное при использовании,
__dict__
может быть значительным.
SQLAlchemy приписывает большую экономию памяти __slots__
.
Чтобы убедиться в этом, с помощью дистрибутива Anaconda Python 2.7 в Ubuntu Linux, с guppy.hpy
(он же heapy) и sys.getsizeof
, размер экземпляра класса без __slots__
объявленного и ничего более, составляет 64 байта. Это не включает в себя __dict__
. Еще раз спасибо Python за ленивую оценку, __dict__
очевидно, он не вызывается, пока на него не ссылаются, но классы без данных обычно бесполезны. При вызове __dict__
атрибут дополнительно содержит минимум 280 байтов.
Напротив, экземпляр класса с __slots__
объявленным()
(без данных) составляет всего 16 байтов, и всего 56 байтов с одним элементом в слотах, 64 с двумя.
Для 64-битного Python я проиллюстрирую потребление памяти в байтах в Python 2.7 и 3.6 для __slots__
и __dict__
(не определены слоты) для каждой точки, где dict возрастает в 3.6 (за исключением атрибутов 0, 1 и 2):
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
Итак, несмотря на меньшие требования в Python 3, мы видим, насколько хорошо __slots__
масштабируются экземпляры, чтобы сэкономить нам память, и это основная причина, по которой вы хотели бы использовать __slots__
.
Просто для полноты моих заметок обратите внимание, что в пространстве имен класса существует разовая стоимость одного слота: 64 байта в Python 2 и 72 байта в Python 3, поскольку в слотах используются дескрипторы данных, такие как свойства, называемые «члены».
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
__slots__
:Чтобы отрицать создание __dict__
, вы должны создать подкласс object
:
class Base(object):
__slots__ = ()
сейчас же:
>>> 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'
Или подкласс другого класса, который определяет __slots__
class Child(Base):
__slots__ = ('a',)
и сейчас:
c = Child()
c.a = 'a'
но:
>>> 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'
Чтобы разрешить __dict__
создание при создании подклассов объектов со слотами , просто добавьте '__dict__'
к ним __slots__
(обратите внимание, что слоты упорядочены, и вы не должны повторять слоты, которые уже находятся в родительских классах):
class SlottedWithDict(Child):
__slots__ = ('__dict__', 'b')
swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'
а также
>>> swd.__dict__
{'c': 'c'}
Или вам даже не нужно объявлять __slots__
в своем подклассе, и вы все равно будете использовать слоты от родителей, но не ограничивать создание __dict__
:
class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'
А также:
>>> ns.__dict__
{'b': 'b'}
Однако __slots__
могут возникнуть проблемы для множественного наследования:
class BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
Поскольку создание дочернего класса от родителей с обоими непустыми слотами завершается неудачно:
>>> 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
Если вы столкнулись с этой проблемой, вы можете просто удалить __slots__
из родителей, или, если у вас есть контроль над родителями, дать им пустые слоты или рефакторинг абстракций:
from 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!
'__dict__'
чтобы __slots__
получить динамическое назначение:class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
и сейчас:
>>> foo = Foo()
>>> foo.boink = 'boink'
Таким образом, с помощью '__dict__'
слотов мы теряем некоторые преимущества по размеру, поскольку у нас есть динамическое назначение и все еще есть слоты для имен, которые мы ожидаем.
Когда вы наследуете от объекта, который не является слотом, вы получаете тот же вид семантики, когда используете __slots__
- имена, которые __slots__
указывают на значения слотов, в то время как любые другие значения помещаются в экземпляры __dict__
.
Избегать, __slots__
потому что вы хотите иметь возможность добавлять атрибуты на лету, на самом деле не является хорошей причиной - просто добавьте"__dict__"
в свой, __slots__
если это требуется.
Вы можете аналогичным образом добавить __weakref__
в__slots__
явном виде , если вам нужна эта функция.
Встроенный namedtuple делает неизменяемые экземпляры очень легкими (по сути, размером кортежей), но чтобы получить преимущества, вам нужно сделать это самостоятельно, если вы подклассируете их:
from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
"""MyNT is an immutable and lightweight object"""
__slots__ = ()
Применение:
>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'
И попытка присвоить неожиданный атрибут вызывает, AttributeError
потому что мы предотвратили создание __dict__
:
>>> nt.quux = 'quux'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'
Вы можете разрешить __dict__
создание, отключившись __slots__ = ()
, но вы не можете использовать непустые __slots__
с подтипами кортежа.
Даже если непустые слоты одинаковы для нескольких родителей, их нельзя использовать вместе:
class Foo(object):
__slots__ = 'foo', 'bar'
class Bar(object):
__slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()
>>> class Baz(Foo, Bar): pass
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
Использование пустого __slots__
в родительском элементе, по-видимому, обеспечивает наибольшую гибкость, позволяя дочернему элементу выбрать предотвращение или разрешение (путем добавления '__dict__'
для получения динамического назначения, см. Раздел выше) создания__dict__
:
class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'
Вам не нужно иметь слоты - поэтому, если вы добавите их и удалите их позже, это не должно вызвать каких-либо проблем.
Рассмотрим здесь : если вы пишете миксины или используете абстрактные базовые классы , которые не предназначены для создания экземпляров, пустое место __slots__
в этих родителях, похоже, является лучшим способом обеспечения гибкости для субклассеров.
Для демонстрации, во-первых, давайте создадим класс с кодом, который мы хотели бы использовать при множественном наследовании.
class AbstractBase:
__slots__ = ()
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})'
Мы могли бы использовать вышеперечисленное напрямую, унаследовав и объявив ожидаемые слоты:
class Foo(AbstractBase):
__slots__ = 'a', 'b'
Но нас это не волнует, это тривиальное одиночное наследование, нам нужен другой класс, от которого мы могли бы также наследовать, возможно, с атрибутом noisy:
class AbstractBaseC:
__slots__ = ()
@property
def c(self):
print('getting c!')
return self._c
@c.setter
def c(self, arg):
print('setting c!')
self._c = arg
Теперь, если на обеих базах были непустые слоты, мы не смогли бы сделать следующее. (На самом деле, если бы мы хотели, мы могли бы дать AbstractBase
непустые слоты a и b и исключить их из нижеследующего объявления - оставить их было бы неправильно):
class Concretion(AbstractBase, AbstractBaseC):
__slots__ = 'a b _c'.split()
И теперь у нас есть функциональность как через множественное наследование, так и можем отрицать __dict__
и __weakref__
инстанцирование:
>>> c = Concretion('a', 'b')
>>> c.c = c
setting c!
>>> c.c
getting c!
Concretion('a', 'b')
>>> c.d = 'd'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Concretion' object has no attribute 'd'
__class__
назначение с другим классом, у которого их нет (и вы не можете добавить их), если макеты слотов не идентичны. (Мне очень интересно узнать, кто это делает и почему.)Вы можете быть в состоянии выявить дальнейшие предостережения из остальной __slots__
документации (документы 3.7 для разработчиков являются наиболее актуальными) последняя , в которую я внес значительный недавний вклад.
Нынешние топ-ответы приводят устаревшую информацию, они довольно волнистые и в некоторых важных аспектах не попадают в цель.
__slots__
при создании множества объектов»Я цитирую:
«Вы захотите использовать,
__slots__
если собираетесь создавать множество (сотни, тысячи) объектов одного и того же класса».
Абстрактные базовые классы, например, из collections
модуля, не создаются, но __slots__
объявляются для них.
Почему?
Если пользователь хочет запретить __dict__
или __weakref__
создать, эти вещи не должны быть доступны в родительских классах.
__slots__
способствует повторному использованию при создании интерфейсов или миксинов.
Это правда, что многие пользователи Python пишут не для повторного использования, но когда вы это делаете, возможность отрицать ненужное использование пространства является ценной.
__slots__
не ломать травлениеКогда вы выбираете щелевой объект, вы можете обнаружить, что он жалуется, вводя в заблуждение TypeError
:
>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
Это на самом деле неверно. Это сообщение приходит от самого старого протокола, который используется по умолчанию. Вы можете выбрать последний протокол с -1
аргументом. В Python 2.7 это будет 2
(что было введено в 2.3), а в 3.6 это так 4
.
>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>
в Python 2.7:
>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>
в Python 3.6
>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>
Так что я бы помнил об этом, так как это решенная проблема.
Первый абзац - наполовину короткое объяснение, наполовину предсказательный. Вот единственная часть, которая на самом деле отвечает на вопрос
Правильное использование
__slots__
для экономии места в объектах. Вместо того, чтобы иметь динамический диктант, который позволяет добавлять атрибуты к объектам в любое время, существует статическая структура, которая не допускает добавления после создания. Это экономит накладные расходы на один дикт для каждого объекта, который использует слоты
Вторая половина - это желаемое за действительное и нестандартное мышление:
Хотя иногда это полезная оптимизация, она была бы совершенно ненужной, если бы интерпретатор Python был достаточно динамичным, чтобы он требовал диктовку только тогда, когда на самом деле были дополнения к объекту.
Python фактически делает что-то похожее на это, только создавая __dict__
когда к нему обращаются, но создавая множество объектов без данных, довольно нелепо.
Второй абзац упрощает и пропускает реальные причины, которых следует избегать __slots__
. Ниже не реальная причина, чтобы избежать слотов (по фактическим причинам, см. Остальную часть моего ответа выше.):
Они изменяют поведение объектов, имеющих слоты, таким образом, что они могут быть использованы злоупотребляющими людьми и статичными типизаторами.
Затем он продолжает обсуждать другие способы достижения этой извращенной цели с Python, не обсуждая ничего общего с __slots__
.
Третий абзац более желаемое за действительное. Вместе это в основном некондиционный контент, который автор даже не написал, и он вносит вклад в боеприпасы для критиков сайта.
Создайте несколько нормальных объектов и щелевых объектов:
>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Создайте миллион из них:
>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Осмотреть с guppy.hpy().heap()
:
>>> guppy.hpy().heap()
Partition of a set of 2028259 objects. Total size = 99763360 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 1000000 49 64000000 64 64000000 64 __main__.Foo
1 169 0 16281480 16 80281480 80 list
2 1000000 49 16000000 16 96281480 97 __main__.Bar
3 12284 1 987472 1 97268952 97 str
...
Получите доступ к обычным объектам и их __dict__
и осмотрите снова:
>>> for f in foos:
... f.__dict__
>>> guppy.hpy().heap()
Partition of a set of 3028258 objects. Total size = 379763480 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 1000000 33 280000000 74 280000000 74 dict of __main__.Foo
1 1000000 33 64000000 17 344000000 91 __main__.Foo
2 169 0 16281480 4 360281480 95 list
3 1000000 33 16000000 4 376281480 99 __main__.Bar
4 12284 0 987472 0 377268952 99 str
...
Это согласуется с историей Python, начиная с унификации типов и классов в Python 2.2
Если вы создаете подкласс встроенного типа, дополнительное пространство автоматически добавляется к экземплярам для размещения
__dict__
и__weakrefs__
. (__dict__
Хотя он не инициализируется до тех пор, пока вы его не используете, поэтому вам не нужно беспокоиться о пространстве, занимаемом пустым словарем для каждого создаваемого вами экземпляра.) Если вам не нужно это дополнительное пространство, вы можете добавить фразу "__slots__ = []
" в твой класс.
class Child(BaseA, BaseB): __slots__ = ('a', 'b')
поймал пример с empy-slot-родители. Почему здесьdictproxy
создано вместо повышенияAttributeError
дляc
?__slots__
. Шутки в сторону! Спасибо!__slots__
около года назад я участвовал в разработке документации по Python : github.com/python/cpython/pull/1819/filesЦитируя Джейкоба Халлена :
источник
__slots__
не решает те же проблемы, что и статическая типизация. Например, в C ++ не ограничивается объявление переменной-члена, а присваивается этой переменной непреднамеренный тип (и принудительно применяется компилятор). Я не одобряю использование__slots__
, просто интересуюсь разговором. Спасибо!Вы хотели бы использовать,
__slots__
если вы собираетесь создавать множество (сотни, тысячи) объектов одного и того же класса.__slots__
существует только как инструмент оптимизации памяти.Настоятельно не рекомендуется использовать
__slots__
для ограничения создания атрибутов.Сборка объектов с
__slots__
не будет работать по умолчанию (самый старый) протокол выбора; необходимо указать более позднюю версию.Также могут быть затронуты некоторые другие особенности самонаблюдения Python.
источник
Каждый объект Python имеет
__dict__
атрибут, который является словарем, содержащим все остальные атрибуты. например, когда выself.attr
печатаете Python на самом делеself.__dict__['attr']
. Как вы можете себе представить, использование словаря для хранения атрибута требует дополнительного пространства и времени для доступа к нему.Однако при использовании
__slots__
любой объект, созданный для этого класса, не будет иметь__dict__
атрибута. Вместо этого весь доступ к атрибутам осуществляется напрямую через указатели.Поэтому, если вам нужна структура стиля C, а не полноценный класс, вы можете использовать ее
__slots__
для уменьшения размера объектов и сокращения времени доступа к атрибутам. Хорошим примером является класс Point, содержащий атрибуты x & y. Если у вас будет много очков, вы можете попробовать использовать их__slots__
для экономии памяти.источник
__slots__
определенным не похож на структуру в стиле C. Существует словарь атрибутов сопоставления имен индексов на уровне классов, иначе следующее было бы невозможным:class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
я действительно думаю, что этот ответ должен быть разъяснен (я могу сделать это, если хотите). Кроме того, я не уверен, чтоinstance.__hidden_attributes[instance.__class__[attrname]]
это быстрее, чемinstance.__dict__[attrname]
.В дополнение к другим ответам, вот пример использования
__slots__
:Таким образом, для реализации
__slots__
требуется только дополнительная строка (и превращение вашего класса в класс нового стиля, если это еще не сделано). Таким образом, вы можете уменьшить объем памяти этих классов в 5 раз , за счет необходимости написания собственного кода, если и когда это будет необходимо.источник
Слоты очень полезны для библиотечных вызовов, чтобы исключить «диспетчеризацию именованных методов» при выполнении вызовов функций. Это упоминается в документации SWIG . Для высокопроизводительных библиотек, которые хотят сократить накладные расходы на функции для часто вызываемых функций, используя слоты намного быстрее.
Теперь это не может быть напрямую связано с вопросом ОП. Это больше относится к созданию расширений, чем к использованию синтаксиса слотов в объекте. Но это помогает завершить картину использования слотов и некоторые причины, стоящие за ними.
источник
Атрибут экземпляра класса имеет 3 свойства: экземпляр, имя атрибута и значение атрибута.
При обычном доступе к атрибуту экземпляр действует как словарь, а имя атрибута выступает в качестве ключа в этом словаре при поиске значения.
экземпляр (атрибут) -> значение
В доступе __slots__ имя атрибута действует как словарь, а экземпляр выступает в качестве ключа в словаре при поиске значения.
атрибут (экземпляр) -> значение
В навесе имя атрибута действует как словарь, а значение выступает в качестве ключа в этом словаре при поиске экземпляра.
атрибут (значение) -> экземпляр
источник
__slots__
?Очень простой пример
__slot__
атрибута.Проблема: без
__slots__
Если у меня нет
__slot__
атрибутов в моем классе, я могу добавить новые атрибуты для своих объектов.Если вы посмотрите на пример выше, вы можете увидеть, что obj1 и obj2 имеют свои собственные атрибуты x и y, а python также создал
dict
атрибут для каждого объекта ( obj1 и obj2 ).Предположим, есть ли в моем классе Test тысячи таких объектов? Создание дополнительного атрибута
dict
для каждого объекта вызовет много накладных расходов (память, вычислительная мощность и т. Д.) В моем коде.Решение: с
__slots__
Теперь в следующем примере мой класс Test содержит
__slots__
атрибут. Теперь я не могу добавлять новые атрибуты в мои объекты (кроме атрибутаx
), и python больше не создаетdict
атрибут. Это устраняет накладные расходы для каждого объекта, которые могут стать значительными, если у вас много объектов.источник
Еще одно не
__slots__
совсем понятное использование - добавить атрибуты в прокси-объект объекта из пакета ProxyTypes, который ранее был частью проекта PEAK. ЭтоObjectWrapper
позволяет вам прокси-объект другого объекта, но перехватывать все взаимодействия с прокси-объектом. Он не очень широко используется (и не поддерживает Python 3), но мы использовали его для реализации поточно-ориентированной блокирующей оболочки вокруг асинхронной реализации, основанной на торнадо, которая перенаправляет весь доступ к проксируемому объекту через ioloop, используя потокобезопасныйconcurrent.Future
объекты для синхронизации и возврата результатов.По умолчанию любой атрибут доступа к прокси-объекту даст вам результат от прокси-объекта. Если вам нужно добавить атрибут на объект прокси,
__slots__
можно использовать.источник
Вы - по существу - бесполезны для
__slots__
.В то время, когда вы думаете, что вам это может понадобиться
__slots__
, вы на самом деле хотите использовать шаблоны проектирования Lightweight или Flyweight . Это случаи, когда вы больше не хотите использовать чисто объекты Python. Вместо этого вы хотите объектно-подобную обертку Python для массива, структуры или массива numpy.Классоподобная оболочка не имеет атрибутов - она просто предоставляет методы, которые воздействуют на базовые данные. Методы могут быть сведены к методам класса. На самом деле, это может быть сведено к функциям, работающим с базовым массивом данных.
источник
__slots__
?__slots__
оба метода оптимизации для экономии памяти.__slots__
показывает преимущества, когда у вас много много объектов, а также шаблон проектирования Flyweight. Оба решают одну и ту же проблему.__slots__
действительно ответ, и, как указывает Евгений, его можно добавить как простую запоздалую мысль (например, сначала вы можете сосредоточиться на правильности, а затем повысить производительность).Первоначальный вопрос был о случаях общего использования, а не только о памяти. Таким образом, здесь следует упомянуть, что вы также получаете лучшую производительность при создании большого количества объектов - это интересно, например, при разборе больших документов на объекты или из базы данных.
Вот сравнение создания деревьев объектов с миллионами записей, используя слоты и без слотов. В качестве ссылки также производительность при использовании простых диктов для деревьев (Py2.7.10 в OSX):
Тестовые классы (идент, аппарт из слотов):
Тест-код, подробный режим:
источник