Сегодня поговорим про небольшую оптимизацию кода по памяти.
Известный факт, что неизменяемые структуры, которые имеют фиксированные размеры, имеют некоторые преимущества в сравнении с изменяемыми.
В рамках проектов, где часто вызываются миллионы мелких объектов (например, узлы в графе, события в симуляции, кастомные токены) проблема утечки памяти встает очень остро. Отчасти, это касается и атрибутов объектов.
По умолчанию 🐍 хранит атрибуты объектов в словаре, включенном в метод
__dict__ (появился он в языке 8 февраля 2012 года), который, как и обычный словарь имеет динамическое выделение памяти и может быть расширяем, что при всех своих плюсах создает большую нагрузку на память. Для решения проблемы предлагается использовать метод
__slots__. Он отключает создание
__dict__ и резервирует память только под конкретные атрибуты, что помогает оптимизировать код по памяти, но жестко фиксирует набор атрибутов объекта, что является одновременно и плюсом и минусом. Рассмотрим на примере:
🔸Определим классический класс
CoordinatesDict`и класс `CoordinatesSlots с использованием __slots__import sys
class CoordinatesDict:
def __init__(self, x, y,z):
self.x = x
self.y = y
self.z = z
class CoordinatesSlots:
__slots__ = ['x', 'y', 'z'] # Фиксируем атрибуты
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
🔸Теперь определим объекты классов
obj1 и obj2:obj1 = CoordinatesDict(1, 2, 3)
obj2 = CoordinatesSlots(1, 2, 3)
- Какие разницы в этих объектах?
🔵В
obj1 можно дополнительно объявить новый атрибут, скажем, dist и присвоить ему значение, он пополнит ряды __dict__. В obj2 аналогичная операция вернет AttributeErrorПример:
obj1.dist = 2
obj1.__dict__
>>>{'x': 1, 'y': 2, 'z': 3, 'dist': 2}obj2.dist = 2
>>>AttributeError: 'CoordinatesSlots' object has no attribute 'dist'
Причина такого поведения лежит именно в формировании класса с использованием
__slots__. Такой класс, как говорилось ранее, хранит атрибуты в массиве фиксированной размерности, отключая возможность работы со словарем атрибутов (то есть у вас не появляется __dict__), а все атрибуты, не записанные в массив, поднимают ошибку. 🔵В obj2 нет словаря атрибутов
__dict__, а следовательно на него не требуется память.Проверить это просто:
print(sys.getsizeof(obj1), sys.getsizeof(obj1.__dict__))
print(sys.getsizeof(obj2))
print(sys.getsizeof(obj2.__dict__))
Вывод следующий (до добавления атрибута):
48 272
56
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/tmp/ipython-input-390784945.py in <cell line: 0>()
18 print(sys.getsizeof(p1), sys.getsizeof(p1.__dict__))
19 print(sys.getsizeof(p2))
---> 20 print(sys.getsizeof(p2.__dict__))
AttributeError: 'CoordinatesSlots' object has no attribute '__dict__'
Таким образом, если мы имеет часто используемый класс и нам не нужна свобода в изменении состава атрибутов, то правильнее использовать конструкцию со
__slots__Ставь 🔥, если не знал.
Ставь👍, если знал.
#ds_лайфхаки