__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?