__slots__ в классе и при наследовании
class AbstractA(ABC):
__slots__ = ()
class AbstractB(ABC):
__slots__ = ()
class BaseA(AbstractA):
__slots__ = ('a',)
class BaseB(AbstractB):
__slots__ = ('b',)
class Child(AbstractA, AbstractB):
__slots__ = ('a', 'b')
c = Child()
1234567891011121314151617То есть мы вместо того, чтобы наследоваться от классов с конкретной реализацией (BaseA, BaseB), наследуемся от абстрактных классов
Однако __slots__ может вызвать проблемы при множественном наследовании.
class BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
12345Создание дочернего класса от родителей с обоими непустыми слотами не удается:
>>> class Child(BaseA, BaseB): __slots__ = ()
Traceback (most recent call last):
File "<pyshell#68>", line 1, in <module>
class Child(BaseA, BaseB): __slots__ = ()
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
123456Если вы столкнетесь с этой проблемой, вы можете просто удалить __slots__ у родителей или, если вы контролируете родителей, дать им пустые слоты или выполнить рефакторинг для абстракций:
from abc import ABC
class AbstractA(ABC):
__slots__ = ()
class BaseA(AbstractA):
__slots__ = ('a',)
class AbstractB(ABC):
__slots__ = ()
class BaseB(AbstractB):
__slots__ = ('b',)
class Child(AbstractA, AbstractB):
__slots__ = ('a', 'b')
c = Child() # Нет ошибок
123456789101112131415161718Добавьте '__dict__' к __slots__ чтобы получить динамическое назначение:
class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
12и сейчас
>>> foo = Foo()
>>> foo.boink = 'boink'
12Таким образом, с '__dict__' в слотах мы теряем некоторые преимущества размера с преимуществом наличия динамического назначения и по-прежнему наличия слотов для имен, которые мы ожидаем
Когда вы наследуете объект, который не имеет слотов, вы получаете такую же семантику, когда используете __slots__ - имена, которые находятся в __slots__, указывают на значения, размещенные в слотах, тогда как любые другие значения помещаются в __dict__ экземпляра.
Избегать __slots__, потому что вы хотите иметь возможность добавлять атрибуты на лету, на самом деле не является хорошей причиной - просто добавьте '__dict__' в свой __slots__, если это необходимо.
Вы можете точно так же явно добавить __weakref__ в __slots__, если вам нужна эта функция.
Подводя итоги: если вы составляете миксины или используете абстрактные базовые классы, которые не предназначены для создания экземпляров, пустой __slots__ в этих родителях кажется лучшим способом гибкости для подклассов.
Чтобы продемонстрировать, сначала давайте создадим код с классом, который мы хотели бы использовать при множественном наследовании.
class AbstractBase:
__slots__ = ()
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})'
1234567Мы могли бы использовать вышесказанное непосредственно путем наследования и объявления ожидаемых слотов:
class Foo(AbstractBase):
__slots__ = 'a', 'b'
12Но нас это не волнует, это тривиальное одиночное наследование, нам нужен другой класс, от которого мы также могли бы унаследовать, возможно, с шумным атрибутом:
class AbstractBaseC:
__slots__ = ()
@property
def c(self):
print('getting c!')
return self._c
@c.setter
def c(self, arg):
print('setting c!')
self._c = arg
12345678910Теперь, если бы на обеих базах были непустые слоты, мы не смогли бы сделать следующее. (На самом деле, если бы мы хотели, мы могли бы дать AbstractBase непустые слоты a и b и исключить их из приведенного ниже объявления - оставлять их было бы неправильно):
class Concretion(AbstractBase, AbstractBaseC):
__slots__ = 'a b _c'.split()
12И теперь у нас есть функциональность от обоих через множественное наследование, и мы все еще можем запретить создание экземпляров __dict__ и __weakref__:
>>> c = Concretion('a', 'b')
>>> c.c = c
setting c!
>>> c.c
getting c!
Concretion('a', 'b')
>>> c.d = 'd'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Concretion' object has no attribute 'd'Last updated
Was this helpful?