Питон:Как на самом деле работает наследование __слотов__ в подклассах?
-
08-07-2019 - |
Вопрос
В Справочный раздел модели данных Python, посвященный слотам существует список примечаний по использованию __slots__
.Я совершенно сбит с толку 1-м и 6-м пунктами, потому что они, кажется, противоречат друг другу.
Первый пункт:
- При наследовании от класса без
__slots__
, тот__dict__
атрибут этого класса всегда будет доступен, поэтому a__slots__
определение в подклассе является бессмысленным.
Шестой пункт:
- Действие a
__slots__
объявление ограничено классом где оно определено.В результате, подклассы будут иметь__dict__
если только они также не определяют__slots__
(который должен содержать только названия любых дополнительных слотов).
Мне кажется, что эти элементы могли бы быть лучше сформулированы или показаны с помощью кода, но я пытался разобраться в этом и все еще нахожусь в замешательстве.Я действительно понимаю, как __slots__
являются предполагается, что он используется, и я пытаюсь лучше понять, как они работают.
Этот Вопрос:
Может кто-нибудь, пожалуйста, объяснить мне простым языком, каковы условия наследования слотов при создании подклассов?
(Простые примеры кода были бы полезны, но не обязательны.)
Решение
Как уже упоминали другие, единственная причина для определения __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
Исходя из этого, казалось бы, размер with-slots равен более крупный чем размер без слотов!Но это ошибка, потому что sys.getsizeof
не учитывает "содержимое объекта", такое как словарь:
>>> sys.getsizeof(n.__dict__)
140
Поскольку один только dict занимает 140 байт, очевидно, что объект "32 байта" n
утверждается, что они принимают во внимание не все, что задействовано в каждом конкретном случае.Вы можете лучше справляться со сторонними расширениями, такими как пимплер:
>>> 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__
для настройка атрибут, но совсем никакой, на самом деле крошечный диспреимущество, для получение это).
Теперь, что касается наследования:для экземпляра, который не должен быть продиктован, ВСЕ классы в его цепочке наследования также должны иметь экземпляры, не содержащие 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
так что на самом деле нет никаких причин делать это.
Другие советы
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__
в ближайшем будущем.Это предназначено только для экономии памяти ценой некоторой гибкости.Если у вас нет десятков тысяч объектов, это не будет иметь значения.
Питон:Как происходит наследование
__slots__
в подклассах действительно работают?Я совершенно сбит с толку 1-м и 6-м пунктами, потому что они, кажется, противоречат друг другу.
Эти пункты на самом деле не противоречат друг другу.Первый касается подклассов классов, которые не реализуют __slots__
, второй касается подклассов классов , которые делай реализовать __slots__
.
Подклассы классов, которые не реализуют __slots__
Я все больше осознаю, что какими бы замечательными ни считались документы Python (по праву), они не идеальны, особенно в том, что касается менее используемых функций языка.Я бы изменил Документы следующим образом:
При наследовании от класса без
__slots__
, тот__dict__
атрибут этого класса всегда будет доступен, так что a.__slots__
определение в подкласс не имеет смысла
__slots__
все еще имеет смысл для такого класса.В нем документируются ожидаемые имена атрибутов класса.Это также создает слоты для этих атрибутов - они будут выполнять более быстрый поиск и занимать меньше места.Это просто допускает другие атрибуты, которые будут присвоены __dict__
.
Это изменение было принято и сейчас находится в последняя документация.
Вот пример:
class Foo:
"""instances have __dict__"""
class Bar(Foo):
__slots__ = 'foo', 'bar'
Bar
в нем есть не только объявленные слоты, но и слоты Foo, которые включают __dict__
:
>>> b = Bar()
>>> b.foo = 'foo'
>>> b.quux = 'quux'
>>> vars(b)
{'quux': 'quux'}
>>> b.foo
'foo'
Подклассы классов , которые делай реализовать __slots__
Действие a
__slots__
объявление ограничено классом, в котором оно определено .В результате подклассы будут иметь__dict__
если только они также не определяют__slots__
(который должен содержать только названия любых дополнительных слотов).
Ну, это тоже не совсем правильно.Действие a __slots__
декларация является не полностью ограничен классом, в котором он определен.Они могут иметь последствия, например, для множественного наследования.
Я бы изменил это на:
Для классов в дереве наследования , которое определяет
__slots__
, подклассы будут иметь__dict__
если только они также не определяют__slots__
(который должен содержать только названия любых дополнительных слотов).
Я действительно обновил его, чтобы прочитать:
Действие a
__slots__
объявление не ограничивается классом где оно определено.__slots__
объявленные в parents доступны в дочерние классы.Однако дочерние подклассы получат__dict__
и__weakref__
если только они также не определяют__slots__
(который должен содержать только названия любых дополнительных слотов).
Вот пример:
class Foo:
__slots__ = 'foo'
class Bar(Foo):
"""instances get __dict__ and __weakref__"""
И мы видим, что подкласс щелевого класса получает возможность использовать слоты:
>>> b = Bar()
>>> b.foo = 'foo'
>>> b.bar = 'bar'
>>> vars(b)
{'bar': 'bar'}
>>> b.foo
'foo'
(Подробнее о __slots__
, смотрите мой ответ здесь.)
Из ответа, на который вы ссылались:
Правильное использование
__slots__
заключается в экономии места в объектах.Вместо того, чтобы иметь динамический диктант...
"При наследовании от класса без __slots__
, тот __dict__
атрибут этого класса всегда будет доступен", поэтому добавляйте свой собственный __slots__
не может запретить объектам иметь __dict__
, и не может сэкономить место.
Немного о __slots__
не быть унаследованным - это немного глупо.Помните, что это магический атрибут и он ведет себя не так, как другие атрибуты, затем перечитайте его как говорящий о том, что это поведение magic slots не наследуется.(На самом деле это все, что от него требуется.)
Мое понимание заключается в следующем:
класс
X
не имеет__dict__
<------->
классX
и все его суперклассы имеют__slots__
указанныйв этом случае фактические слоты класса состоят из объединения
__slots__
декларации дляX
и его суперклассы;поведение не определено (и станет ошибкой), если это объединение не является непересекающимся