Python: Как на самом деле работает наследование __slots__ в подклассах? - PullRequest
59 голосов
/ 29 ноября 2009

В справочном разделе Модель данных Python о слотах есть список примечаний по использованию __slots__. Я полностью смущен 1-м и 6-м пунктами, потому что они, кажется, противоречат друг другу.

Первый предмет:

  • При наследовании от класса без __slots__, атрибут __dict__ этого класса всегда будет доступно, так что __slots__ определение в подклассе бессмысленны.

Шестой элемент:

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

Мне кажется, что эти пункты могли бы быть лучше сформулированы или показаны в коде, но я пытался обернуть это вокруг головы и все еще растерялся. Я понимаю, как __slots__ предполагается использовать , и я пытаюсь лучше понять, как они работают.

Вопрос:

Может кто-нибудь объяснить мне простым языком, каковы условия наследования слотов при создании подклассов?

(простые примеры кода были бы полезны, но не обязательны.)

Ответы [ 5 ]

103 голосов
/ 29 ноября 2009

Как уже упоминали другие, единственная причина для определения __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

так что на самом деле нет причин делать это.

13 голосов
/ 29 ноября 2009
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__ в ближайшем будущем. Он предназначен только для экономии памяти за счет некоторой гибкости. Если у вас нет десятков тысяч объектов, это не будет иметь значения.

4 голосов
/ 17 августа 2016

Python: Как на самом деле работает наследование __slots__ в подклассах?

Меня смущают 1-й и 6-й пункты, потому что они, кажется, противоречат друг другу.

Эти предметы на самом деле не противоречат друг другу. Первый касается подклассов классов, которые не реализуют __slots__, второй касается подклассов классов, которые делают реализуют __slots__.

Подклассы классов, которые не реализуют __slots__

Я все больше осознаю, что, несмотря на то, что документы Python (по праву) считаются великолепными, они не идеальны, особенно в отношении менее используемых функций языка. Я бы изменил документы следующим образом:

При наследовании от класса без __slots__, атрибут __dict__ этого класса всегда будет доступен , поэтому определение __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__

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

Ну, это тоже не совсем верно. Действие объявления __slots__ не полностью ограничено классом, в котором оно определено. Например, они могут иметь значение для множественного наследования.

Я бы изменил это на:

Для классов в дереве наследования, которое определяет __slots__, подклассы будут иметь __dict__, если только они также определить __slots__ (который должен содержать только имена любых дополнительных слоты). * 1 075 *

Я на самом деле обновил его так:

Действие декларации __slots__ не ограничено классом где это определено. __slots__ заявленные в родителях доступны в детские классы. Тем не менее, дочерние подклассы получат __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__, см. Мой ответ здесь .)

2 голосов
/ 29 ноября 2009

Из ответа вы связались:

Правильное использование __slots__ для экономии места на объектах. Вместо того, чтобы динамически диктовать ...

«При наследовании от класса без __slots__ атрибут __dict__ этого класса всегда будет доступен», поэтому добавление собственного __slots__ не может помешать объектам иметь __dict__ и не может сэкономить место.

Бит о том, что __slots__ не наследуется, немного туповат. Помните, что это магический атрибут и он не ведет себя как другие атрибуты, а затем перечитайте его, сказав, что это магическое поведение слотов не наследуется. (Это действительно все, что нужно.)

1 голос
/ 29 ноября 2009

Мое понимание таково:

  • класс X не имеет __dict__ <-------> класс X и все его суперклассы имеют __slots__ указанное

  • в этом случае фактические слоты класса состоят из объединения объявлений __slots__ для X и его суперклассов; поведение не определено (и станет ошибкой), если это объединение не является непересекающимся

...