Специальный атрибут __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'