Специальный атрибут __slots__
позволяют явно объявлять элементы данных (например, свойства) и запрещать создание словаря __dict__
и __weakref__
(если явно не объявлено в __slots__
или не доступно в родительском элементе).
Пространство, сэкономленное от НЕ использования __dict__
, может быть значительным! Скорость поиска атрибутов также может быть значительно увеличена.
Наличие магического атрибута __slots__
делает несколько вещей. Во-первых, он ограничивает допустимый набор имен атрибутов объекта только перечисленными именами. Во-вторых, поскольку атрибуты теперь фиксированы, больше нет необходимости хранить атрибуты в словаре экземпляра, поэтому атрибут __dict__
удаляется (если только базовый класс уже не имеет его; он также может быть добавлен обратно подклассом, который не имеет __slots__
). При использовании атрибута __slots__
атрибуты хранятся в заранее определенных местах в массиве. Таким образом, каждый атрибут слота на самом деле является объектом дескриптора, который знает, как установить/получить каждый атрибут с помощью индекса массива. Реализация этой функции полностью на языке C и очень эффективна.
__slots__
.__slots__
атрибуты __dict__
и __weakref__
экземпляров всегда будут доступны.__dict__
, экземплярам нельзя назначить новые переменные/атрибуты, не указанные в определении __slots__
. При попытке присвоения имени переменной, не указанной в списке, возникает ошибка AttributeError
. Если требуется динамическое присвоение новых переменных, добавьте значение '__dict__'
к последовательности строк в объявлении атрибута __slots__
.__weakref__
для каждого экземпляра, классы, определяющие __slots__
, не поддерживают слабые ссылки на его экземпляры. Если требуется поддержка слабых ссылок, добавьте значение '__weakref__'
к последовательности строк в объявлении атрибута __slots__
.__slots__
реализуются на уровне класса путем создания дескрипторов для каждого имени переменной. В результате атрибуты класса не могут использоваться для установки значений по умолчанию для переменных экземпляра, определенных в методе __slots__
, в противном случае атрибут класса перезапишет назначение дескриптора.__slots__
не ограничивается классом, в котором он определен. Атрибуты __slots__
, объявленные в родительских классах, доступны в дочерних классах. Однако дочерние подклассы получат __dict__
и __weakref__
, если они не определят __slots__
(который должен содержать только имена любых дополнительных слотов).__slots__
не работает для классов, производных, от встроенных типов переменной длины, таких как int
, bytes
и tuple
.__slots__
может быть назначен любой нестроковый итерируемый объект. Также могут использоваться словари, при этом в будущем, значениям, соответствующим каждому ключу, может быть присвоено особое значение.__class__
работает, только если оба класса имеют одинаковые __slots__
.TypeError
.__slots__
используется итератор, то для каждого значения итератора будет создаваться дескриптор, при этом атрибут __slots__
будет пустым итератором.__slots__
.class Slot: __slots__ = ["a", "b"] >>> slot = Slot() >>> slot.a = 1 >>> slot.a # 1 >>> slot.a = 130 >>> slot.a # 130 >>> slot.b = 13 >>> slot.b # 13 # атрибут `c` не определен в списке `__slots__` >>> slot.c = 1 # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # AttributeError: 'Slot' object has no attribute 'c' >>> slot.__dict__ # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # AttributeError: 'Slot' object has no attribute '__dict__'
Чтобы сохранить динамическое назначение переменных для класса, то необходимо добавить значение '__dict__'
в кортеж атрибута __slots__
:
class Test(object): __slots__ = ('a', 'b', '__dict__') >>> t = Test # переменные `foo` и `baz` не # определены в `__slots__` >>> t.foo, t.baz = 10, 100 >>> t.foo, t.baz # (10, 100)
Если в классе будет использоваться функциональность слабых ссылок на объекты, то ее так же можно добавить добавив значение '__weakref__'
в кортеж атрибута __slots__
.
__slots__
происходит более быстрый доступ к атрибутам:import timeit class Foo(object): __slots__ = 'foo', class Bar(object): pass slotted = Foo() not_slotted = Bar() def get_set_delete_fn(obj): def get_set_delete(): obj.foo = 'foo' obj.foo del obj.foo return get_set_delete # измерим скорость доступа к атрибутам >>> min(timeit.repeat(get_set_delete_fn(slotted))) 0.0844320549999793 >>> min(timeit.repeat(get_set_delete_fn(not_slotted))) 0.10562241299999187
__slots__
при множественном наследовании.Внимание! Если в слотах у нескольких родителей определены одинаковые атрибуты, то их нельзя использовать вместе при множественном наследовании:
class Foo(object): __slots__ = 'foo', 'bar' class Bar(object): __slots__ = 'foo', 'bar' # будет работать если в одном из # родительских классов `__slots__ = ()` >>> class Baz(Foo, Bar): pass ... # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # TypeError: multiple bases have instance lay-out conflict
Использование пустого __slots__
в родительском классе, обеспечивает наибольшую гибкость, позволяя дочерним классам выбирать, предотвращать или разрешать создание __dict__
(для динамического назначения атрибутов итогового класса):
class Foo(object): __slots__ = () class Bar(object): __slots__ = () class Baz(Foo, Bar): __slots__ = ('foo', 'bar') b = Baz() b.foo, b.bar = 'foo', 'bar'
Не обязательно иметь __slots__
с определенными атрибутами в родительских классах и если их добавить позже в дочернем классе, то это не должно вызвать никаких проблем.
Если составлять миксины или использовать абстрактные базовые классы, которые не предназначены для создания экземпляров напрямую, то пустой __slots__
в этих родителях - будет лучшим вариантом для подклассов.
Создадим класс с кодом, который будем использовать при множественном наследовании.
class AbstractBase: __slots__ = () def __init__(self, a, b): self.a = a self.b = b
При одиночном наследовании, можно использовать AbstractBase
как родительский класс, а в дочернем классе, в его __slots__
объявить ожидаемые атрибуты:
class Foo(AbstractBase): __slots__ = 'a', 'b'
Разбираемся с множественным наследованием. Определим другой класс, от которого можно наследоваться:
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
Теперь, если у обоих базовых классов были слоты со своими атрибутами, то невозможно было бы сделать следующее.
class TestChild(AbstractBase, AbstractBaseC): __slots__ = 'a b _c'.split()
Фактически, можно было в AbstractBase
оставить __slots__ = 'a', 'b'
и исключить их из объявления класса TestChild()
, но оставлять их было бы неправильно. Зато теперь класс TestChild()
имеет функциональность от обоих родительских классов через множественное наследование, и по-прежнему можно запретить создание экземпляров __dict__
и __weakref__
:
>>> test = TestChild('a', 'b') >>> test.c = 'c' # setting c! >>> test.c # getting c! # 'c' # атрибут не определен >>> test.d = 'd' # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # AttributeError: 'TestChild' object has no attribute 'd'