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


на Python Data model reference section on slots есть список заметок по использованию __slots__. Я полностью смущен 1-м и 6-м пунктами, потому что они, кажется, противоречат друг другу.

первый пункт:

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

шестой пункт:

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

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

Вопрос:

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

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

5 54

5 ответов:

как уже упоминалось, единственная причина для определения __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__ на задание атрибут, но не совсем, действительно крошечный СОПпреимущества для начало его).

теперь, что касается наследования: для экземпляра, чтобы быть дикт-менее,все классы до его наследования цепочка также должна иметь экземпляры без диктанта. Классы с экземплярами без dict-это те, которые определяют __slots__, плюс большинство встроенных типов (встроенные типы, экземпляры которых имеют dicts,-это те, на экземплярах которых вы можете установить произвольные атрибуты, такие как функции). Перекрытия в именах слотов не запрещены, но они бесполезны и тратят некоторую память, так как слоты наследуются:

>>> class A(object): __slots__='a'
... 
>>> class AB(A): __slots__='b'
... 
>>> ab=AB()
>>> ab.a = ab.b = 23
>>> 

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

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

я полностью запутался в 1-м и 6-м пунктах, потому что они, кажется, противоречат друг другу.

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

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

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

при наследовании от класса без __slots__ на

из ответа, который вы связали:

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

" при наследовании от класса без __slots__ на __dict__ атрибут этого класса всегда будет доступен", поэтому добавьте свой собственный __slots__ нельзя запретить объектам иметь __dict__, и не может сохранить космос.

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

в моем понимании это выглядит следующим образом:

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

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