__slots__, pympler и гибридные моделиПро
__slots__ пишут часто, но в реальных проектах с сотнями классов можно нарваться. Не всё так радужно.Оверхед словаря и мифы
По умолчанию каждый экземпляр держит атрибуты в
__dict__. Словарь с хеш-таблицей для миллионов объектов - это 50-70% оверхеда. __slots__ фиксирует список атрибутов на уровне класса и убирает __dict__ (если не добавить вручную). Экономия до 40-60%. Но в больших кодовых базах всплывают грабли:- наследование: у родителя нет
__slots__, у наследника есть - __dict__ все равно создается, выигрыша нет- жесткость: нельзя динамически добавить атрибут - ломаются ORM, сериализаторы, половина библиотек
- дебаг: без
__dict__ не увидишь текущие поля экземпляра в отладчике, приходится лезть в классПрофилирование через pympler
Вместо гаданий - профилирование через
pympler. Пример:from pympler import asizeof
class WithSlots:
__slots__ = ('x', 'y')
def __init__(self):
self.x = 10
self.y = 20
class WithoutSlots:
def __init__(self):
self.x = 10
self.y = 20
print(asizeof.asizeof(WithSlots())) # ~72 байта
print(asizeof.asizeof(WithoutSlots())) # ~184 байта
Цифры не врут: видно, где оверхед реально критичен, а где можно забить.
Гибридная модель:
__slots__ + __dict__Когда часть объектов динамическая (dataclass с optional полями), добавляем
__dict__ в __slots__:class FlexibleSlots:
__slots__ = ('id', 'name', '__dict__')
def __init__(self, id, name, **kwargs):
self.id = id
self.name = name
for k, v in kwargs.items():
setattr(self, k, v)
obj = FlexibleSlots(1, 'test', extra_field=42)
print(obj.extra_field) # 42
Жесткие поля - без оверхеда словаря, динамические - через
__dict__. Памяти чуть больше, но гибкость остается.Что я обычно делаю:
- профилирую не на старте, а когда память реально начинает течь (pympler.muppy, pympler.summary)
- для тысяч объектов - ORM-модели, объекты игр - обязательно
__slots__- если dataclass с наследованием -
@dataclass(slots=True) (Python 3.10+), но контролирую дерево наследования- никогда не пихаю
__slots__ в классы, которые подмешиваются через MRO или передаются в гибкие фреймворкиВывод:
__slots__ - мощный инструмент для сокращения памяти, но его применение требует профилирования и осознанного проектирования, особенно при наследовании и в гибридных сценариях с __dict__.