Как уже упоминали другие, единственная причина для определения __slots__
состоит в том, чтобы сохранить некоторую память, когда у вас есть простые объекты с предопределенным набором атрибутов, и вы не хотите, чтобы каждый носил с собой словарь. Конечно, это имеет смысл только для классов, у которых вы планируете иметь много экземпляров.
Экономия может быть не сразу очевидна - подумайте ...:
>>> class NoSlots(object): pass
...
>>> n = NoSlots()
>>> class WithSlots(object): __slots__ = 'a', 'b', 'c'
...
>>> w = WithSlots()
>>> n.a = n.b = n.c = 23
>>> w.a = w.b = w.c = 23
>>> sys.getsizeof(n)
32
>>> sys.getsizeof(w)
36
Из этого может показаться, что размер со слотами на больше , чем размер без слотов! Но это ошибка, потому что sys.getsizeof
не учитывает «содержимое объекта», такое как словарь:
>>> sys.getsizeof(n.__dict__)
140
Поскольку только дикт должен занимать 140 байт, очевидно, что объект "101 байт", который, как предполагается, принимает n
, не учитывает все, что задействовано в каждом случае. Вы можете лучше работать со сторонними расширениями, такими как pympler :
>>> import pympler.asizeof
>>> pympler.asizeof.asizeof(w)
96
>>> pympler.asizeof.asizeof(n)
288
Это гораздо более четко показывает объем памяти, который сохраняется с помощью __slots__
: для простого объекта, такого как этот случай, он составляет чуть менее 200 байт, что составляет почти 2/3 от общей площади объекта. Теперь, так как в наши дни мегабайт более или менее не имеет большого значения для большинства приложений, это также говорит о том, что __slots__
не стоит беспокоиться, если вы собираетесь иметь всего несколько тысяч экземпляров в время - однако, для миллионов случаев, это действительно имеет очень важное значение. Вы также можете получить микроскопическое ускорение (частично из-за лучшего использования кэша для небольших объектов с __slots__
):
$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x'
10000000 loops, best of 3: 0.37 usec per loop
$ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x'
1000000 loops, best of 3: 0.604 usec per loop
$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x=45'
1000000 loops, best of 3: 0.28 usec per loop
$ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x=45'
1000000 loops, best of 3: 0.332 usec per loop
, но это в некоторой степени зависит от версии Python (эти числа я измеряю многократно с 2,5; с 2,6 я вижу большее относительное преимущество по сравнению с __slots__
для установки атрибута, но ни одного вообще действительно крошечное преимущество dis для получения этого).
Теперь, что касается наследования: для того, чтобы экземпляр был без диктов, все классы в его цепочке наследования также должны иметь экземпляры без диктов. Классы с экземплярами без диктов - это те, которые определяют __slots__
, плюс большинство встроенных типов (встроенные типы, экземпляры которых имеют dict, - это те, для экземпляров которых вы можете устанавливать произвольные атрибуты, такие как функции). Перекрытия в именах слотов не запрещены, но они бесполезны и тратят часть памяти, поскольку слоты наследуются:
>>> class A(object): __slots__='a'
...
>>> class AB(A): __slots__='b'
...
>>> ab=AB()
>>> ab.a = ab.b = 23
>>>
Как видите, вы можете установить атрибут a
для экземпляра AB
- AB
сам определяет только слот b
, но он наследует слот a
из A
. Повторение унаследованного слота не запрещено:
>>> class ABRed(A): __slots__='a','b'
...
>>> abr=ABRed()
>>> abr.a = abr.b = 23
но тратит немного памяти:
>>> pympler.asizeof.asizeof(ab)
88
>>> pympler.asizeof.asizeof(abr)
96
так что на самом деле нет причин делать это.