Короткий ответ
__slots__ объявляет фиксированный, заранее известный набор атрибутов экземпляра и запрещает Python создавать для каждого объекта отдельный __dict__ для произвольных атрибутов — это уменьшает потребление памяти на больших количествах экземпляров, но не позволяет добавлять атрибуты, не перечисленные в __slots__, и требует аккуратности при множественном наследовании.
Как это работает подробнее
Без __slots__ каждый экземпляр хранит свой словарь атрибутов __dict__, который сам по себе занимает заметный объём памяти, особенно при создании миллионов небольших объектов. __slots__ = ("x", "y") заставляет Python выделить для каждого атрибута компактный дескриптор на уровне класса (slot), а у экземпляров не создаётся __dict__ вообще, что экономит память и немного ускоряет доступ к атрибутам.
Пример кода
class PointSlots:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
p = PointSlots(1, 2)
print(p.x, p.y) # 1 2
try:
p.z = 3 # нет в __slots__
except AttributeError as e:
print("Ошибка:", e)
import sys
class PointDict:
def __init__(self, x, y):
self.x = x
self.y = y
print(sys.getsizeof(PointSlots(1, 2)) <= sys.getsizeof(PointDict(1, 2))) # True (обычно)Что использовать на практике
- Практика. __slots__ имеет смысл применять к классам, которые создаются в больших количествах (элементы кэша, узлы структур данных, DTO в горячем пути), и для которых набор атрибутов точно фиксирован. Если нужно наследование от класса без __slots__ (например, от стандартного Exception без явного slots) или динамическое добавление атрибутов — экономия частично или полностью теряется.
Подводные камни
- Риски. Пытаются присвоить атрибут, не указанный в
__slots__, и получаютAttributeError. Также забывают, что при множественном наследовании нескольких классов с непустыми и несовместимыми__slots__Python может поднятьTypeErrorиз-за конфликта layout памяти, а при наследовании от класса без__slots__потомок всё равно получает__dict__, и экономия памяти исчезает.