Python: Как работает наследование __slots__ в подклассах?
В справочной части модели данных Python на слотах есть список заметок об использовании __slots__
. Я полностью смущен 1-м и 6-м пунктами, потому что они, кажется, противоречат друг другу.
Первый элемент:
- При наследовании от класса без
__slots__
, атрибут __dict__
этого класса всегда будет
доступный, поэтому a __slots__
определение в подклассе
бессмысленны.
Шестой элемент:
- Действие a
__slots__
объявление ограничивается классом
где он определен. В результате,
подклассы будут иметь __dict__
если они также не определяют __slots__
(который должен содержать только имена любых
дополнительные слоты).
Мне кажется, что эти предметы можно было лучше сформулировать или показать с помощью кода, но я пытался обвести голову вокруг этого, и я все еще смущен. Я понимаю, как __slots__
предполагается использовать, и я пытаюсь лучше понять, как они работают.
Вопрос:
Может кто-нибудь, пожалуйста, объясните мне простым языком, каковы условия для наследования слотов при подклассификации?
(Простые примеры кода были бы полезны, но не обязательно.)
Ответы
Ответ 1
Как упоминалось выше, единственной причиной определения __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
Поскольку сам dict содержит 140 байт, очевидно, что объект "32 байта" 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, для получения).
Теперь, в отношении наследования: для экземпляра без ограничений, все классы в своей цепочке наследования также должны иметь экземпляры без dict. Классы с бездисковыми экземплярами - это те, которые определяют __slots__
, а также большинство встроенных типов (встроенные типы, чьи экземпляры имеют dicts - это те, на экземплярах которых вы можете установить произвольные атрибуты, такие как функции). Перекрытия в именах слотов не запрещены, но они бесполезны и теряют память, поскольку слоты наследуются:
>>> 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
поэтому нет причин для этого.
Ответ 2
class WithSlots(object):
__slots__ = "a_slot"
class NoSlots(object): # This class has __dict__
pass
Первый элемент
class A(NoSlots): # even though A has __slots__, it inherits __dict__
__slots__ = "a_slot" # from NoSlots, therefore __slots__ has no effect
Шестой пункт
class B(WithSlots): # This class has no __dict__
__slots__ = "some_slot"
class C(WithSlots): # This class has __dict__, because it doesn't
pass # specify __slots__ even though the superclass does.
Вероятно, вам не понадобится использовать __slots__
в ближайшем будущем. Он предназначен только для экономии памяти за счет некоторой гибкости. Если у вас нет десятков тысяч объектов, это не имеет значения.
Ответ 3
Из полученного ответа:
Правильное использование __slots__
- это экономить место в объектах. Вместо того, чтобы иметь динамический dict...
"При наследовании от класса без __slots__
атрибут __dict__
этого класса всегда будет доступен", поэтому добавление собственного __slots__
не может помешать объектам иметь __dict__
и не может сэкономить место.
Бит о __slots__
, не унаследованный, немного тупой. Помните, что это волшебный атрибут и не ведет себя как другие атрибуты, а затем перечитывает это, говоря, что это поведение волшебных слотов не наследуется. (Это действительно все для этого.)
Ответ 4
Мое понимание таково:
-
class X
не имеет __dict__
<------->
class X
, а его суперклассы имеют __slots__
указанный
-
в этом случае фактические слоты класса состоят из объединения объявлений __slots__
для X
и его суперклассов; поведение undefined (и станет ошибкой), если это объединение не является непересекающимся
Ответ 5
Python: Как действительно наследование __slots__
в подклассах действительно работает?
Я полностью смущен 1-м и 6-м пунктами, потому что они, кажется, противоречат друг другу.
Эти предметы фактически не противоречат друг другу. Первый относится к подклассам классов, которые не реализуют __slots__
, второй относится к подклассам классов, которые реализуют __slots__
.
Подклассы классов, которые не реализуют __slots__
Я все больше понимаю, что, как и полагалось, документы Python (по праву), они не идеальны, особенно в отношении менее используемых функций языка. Я бы изменил docs следующим образом:
При наследовании от класса без __slots__
атрибут __dict__
этого класса всегда будет доступно , поэтому определение __slots__
в подкласс не имеет смысла.
__slots__
по-прежнему имеет значение для такого класса. Он документирует ожидаемые имена атрибутов класса. Он также создает слоты для этих атрибутов - они получат более быстрый поиск и используют меньше места. Он просто позволяет использовать другие атрибуты, которые будут назначены __dict__
.
Подклассы классов, которые реализуют __slots__
Действие объявления __slots__
ограничено классом, в котором он определено. В результате подклассы будут иметь __dict__
, если они не будут также определить __slots__
(который должен содержать только имена любых дополнительных слотов).
Ну, это тоже не совсем правильно. Действие объявления __slots__
не ограничивается только классом, где он определен. Например, они могут иметь последствия для множественного наследования.
Я бы изменил это на:
Для классов в дереве наследования, которые определяют подклассы __slots__
,, будут иметь __dict__
, если они не будут также определить __slots__
(который должен содержать только имена любых дополнительных слотов).
(Подробнее о __slots__
, см. мой ответ здесь.)