Питон — объектный язык. Это здорово и удобно, пока не придется создать 10 млн объектов в памяти, которые благополучно ее и съедят. Поговорим о том, как уменьшить аппетит.
Допустим, есть у вас простенький объект «питомец» с атрибутами «имя» (строка) и «стоимость» (целое). Интуитивно кажется, что самое компактное предоставление — в виде кортежа:
("Frank the Pigeon", 50000)Замерим, сколько займет в памяти один такой красавчик:
def fields():
# генерит name длины 10
# и price до 100К
# ...
return (name, price)
n = 10_000
pets = [fields() for _ in range(n)]
size = round(asizeof(pets) / n)
print(f"Pet size (tuple) = {size} bytes")
Pet size (tuple) = 161 bytes
161 байт. Будем использовать как основу для сравнения.
С чистыми кортежами, конечно, работать неудобно. Наверняка вы используете датакласс:
@dataclass
class PetData:
name: str
price: int
А что у него с размером?
Pet size (dataclass) = 257 bytes
x1.60 to baseline
Ого, какой толстенький!
Попробуем использовать именованный кортеж:
class PetTuple(NamedTuple):
name: str
price: int
Pet size (named tuple) = 161 bytes
x1.00 to baseline
Теперь вы понимаете, за что я его так люблю. Удобный интерфейс как у датакласса — а вес как у кортежа. Идеально.
Или нет? В Python 3.10 приехали датаклассы со слотами:
@dataclass(slots=True)
class PetData:
name: str
price: int
Pet size (slots dataclass) = 153 bytes
x0.95 to baseline
Ого! Магия слотов создает специальные худощавые объекты, у которых внутри нет словаря, в отличие от обычных питонячих объектов. И такой датакласс ничуть не уступает кортежу.
Что делать, если 3.10 вам еще не завезли? Использовать
NamedTuple. Или прописывать слоты вручную:@dataclass
class PetData:
__slots__ = ("name", "price")
name: str
price: int
У слотовых объектов есть свои недостатки. Но они отлично подходят для простых случаев (без наследования и прочих наворотов).
P.S. Конечно, настоящий победитель — numpy-массив. Но с ним неинтересно соревноваться ツ
песочница
#stdlib