Ответ
__slots__
в Python — это специальный атрибут класса, который позволяет явно объявить набор допустимых атрибутов для экземпляров этого класса. Его основное назначение — оптимизация использования памяти и, в некоторых случаях, ускорение доступа к атрибутам.
Как это работает:
По умолчанию каждый экземпляр класса в Python имеет словарь __dict__
, который хранит все его атрибуты. Этот словарь занимает дополнительную память. Когда вы определяете __slots__
, Python вместо __dict__
резервирует фиксированное пространство для каждого атрибута, указанного в __slots__
, что значительно сокращает потребление памяти, особенно для классов с большим количеством экземпляров. Также __slots__
предотвращает создание __weakref__
для экземпляров, если он не указан явно в __slots__
.
Преимущества:
- Экономия памяти: Значительное сокращение потребления памяти, так как экземпляры не хранят
__dict__
. Это особенно критично для приложений, создающих миллионы объектов. - Ускорение доступа к атрибутам: Доступ к атрибутам через
__slots__
может быть быстрее, чем через__dict__
, поскольку Python не нужно выполнять поиск в словаре.
Ограничения и особенности:
- Невозможность динамического добавления атрибутов: После определения
__slots__
вы не сможете добавлять новые атрибуты к экземплярам, кроме тех, что перечислены. Попытка сделать это вызоветAttributeError
. - Наследование:
- Если дочерний класс не определяет
__slots__
, он будет иметь__dict__
. - Если дочерний класс определяет
__slots__
, он должен включать атрибуты родительского__slots__
(или Python будет использовать__dict__
для родительских атрибутов). - Если вы хотите, чтобы экземпляры могли иметь
__dict__
или__weakref__
в дополнение к__slots__
, их нужно явно включить в список__slots__
.
- Если дочерний класс не определяет
- Множественное наследование: Может быть сложным, если несколько базовых классов имеют
__slots__
.
Пример:
class PersonWithSlots:
__slots__ = ['name', 'age'] # Разрешенные атрибуты
def __init__(self, name, age):
self.name = name
self.age = age
class PersonWithoutSlots:
def __init__(self, name, age):
self.name = name
self.age = age
p_slots = PersonWithSlots("Alice", 30)
print(f"PersonWithSlots: {p_slots.name}, {p_slots.age}")
# print(p_slots.__dict__) # AttributeError: 'PersonWithSlots' object has no attribute '__dict__'
try:
p_slots.job = "Engineer"
except AttributeError as e:
print(f"Ошибка при добавлении нового атрибута: {e}")
p_no_slots = PersonWithoutSlots("Bob", 25)
print(f"PersonWithoutSlots: {p_no_slots.name}, {p_no_slots.age}")
print(f"__dict__ PersonWithoutSlots: {p_no_slots.__dict__}") # {'name': 'Bob', 'age': 25}
p_no_slots.job = "Developer" # OK
print(f"PersonWithoutSlots с новым атрибутом: {p_no_slots.job}")
Когда использовать __slots__
:
Используйте __slots__
только тогда, когда вы работаете с большим количеством экземпляров класса (тысячи или миллионы) и точно знаете, какие атрибуты будут у ваших объектов. В большинстве обычных случаев его использование не требуется и может усложнить код.