Добавить в корзинуПозвонить
Найти в Дзене

🐍 Как не потерять всю память на JSON: практические советы для Python-разработчиков

Любой, кто работал с большими JSON-файлами в Python, знает, как стремительно может закончиться оперативная память. А если вы пользуетесь Pydantic, который завоевал популярность благодаря удобству и простоте, то, скорее всего, уже сталкивались с неприятной ситуацией, когда даже средний по размеру файл превращается в прожорливого монстра, способного съесть всю доступную память. Недавно Итамар Тернер-Троринг поделился в блоге Python⇒Speed полезными рекомендациями, как победить эту проблему и не исчерпать ресурсы памяти при работе с Pydantic и большими JSON-файлами. Я внимательно изучил его подходы и хочу дополнить их собственным опытом. Возьмём JSON-файл размером около 100 мегабайт. Казалось бы, небольшой объём данных. Однако, если вы просто прочитаете этот файл и загрузите его в модель Pydantic через стандартный метод: directory = CustomerDirectory.model_validate_json(raw_json) то неожиданно обнаружите, что приложение потребляет аж до 2000 MB памяти! 🤯 То есть в 20 раз больше размера ис
Оглавление
Лента с «скобочным» JSON втягивается в трубу и сжимается в аккуратные кубики данных; рядом шкала памяти падает из красной зоны в зелёную — визуальный акцент на том, как приём из статьи снижает потребление ОЗУ при загрузке больших JSON-файлов в Pydantic.
Лента с «скобочным» JSON втягивается в трубу и сжимается в аккуратные кубики данных; рядом шкала памяти падает из красной зоны в зелёную — визуальный акцент на том, как приём из статьи снижает потребление ОЗУ при загрузке больших JSON-файлов в Pydantic.

Любой, кто работал с большими JSON-файлами в Python, знает, как стремительно может закончиться оперативная память. А если вы пользуетесь Pydantic, который завоевал популярность благодаря удобству и простоте, то, скорее всего, уже сталкивались с неприятной ситуацией, когда даже средний по размеру файл превращается в прожорливого монстра, способного съесть всю доступную память.

Недавно Итамар Тернер-Троринг поделился в блоге Python⇒Speed полезными рекомендациями, как победить эту проблему и не исчерпать ресурсы памяти при работе с Pydantic и большими JSON-файлами. Я внимательно изучил его подходы и хочу дополнить их собственным опытом.

📛 Почему вообще возникает проблема?

Возьмём JSON-файл размером около 100 мегабайт. Казалось бы, небольшой объём данных. Однако, если вы просто прочитаете этот файл и загрузите его в модель Pydantic через стандартный метод:

directory = CustomerDirectory.model_validate_json(raw_json)

то неожиданно обнаружите, что приложение потребляет аж до 2000 MB памяти! 🤯 То есть в 20 раз больше размера исходного файла. Это происходит по двум причинам:

  • 🐘 Парсинг JSON: большинство библиотек целиком загружают JSON-файл в память.
  • 🧱 Создание объектов Python: стандартные классы Python не очень экономны по памяти, особенно если объектов много.

🛠️ Как бороться с прожорливостью?

Итамар предлагает три подхода, которые, применённые вместе, дают впечатляющие результаты.

🕹️ Способ 1: Стриминг JSON с библиотекой ijson

Первым шагом можно отказаться от обычного парсинга JSON, заменив его на инкрементальный парсинг с библиотекой ijson. Она читает файл небольшими порциями, не загружая сразу весь файл в память:

import ijson

with open("customers.json", "rb") as f:
data = {}
for cid, cust_dict in ijson.kvitems(f, ""):
customer = Customer.model_validate(cust_dict)
data[cid] = customer
directory = CustomerDirectory.model_validate(data)

✅ Результат: уже экономим память, снижая потребление с 2000 MB до 1200 MB. Правда, это замедляет процесс примерно в 5 раз, но иногда лучше медленнее и стабильнее.

🧩 Способ 2: dataclasses и магия slots

Второй приём — переход от классических моделей Pydantic (BaseModel) к использованию dataclasses с параметром slots=True. Слоты — это способ сообщить Python, что атрибуты объектов заранее известны и фиксированы, и дополнительные атрибуты добавлять нельзя. Благодаря этому объекты занимают гораздо меньше памяти.

Вот так выглядит улучшенная модель с dataclasses и слотами:

from pydantic import RootModel
from pydantic.dataclasses import dataclass

@dataclass(slots=True)
class Name:
first: str | None
last: str | None

@dataclass(slots=True)
class Customer:
id: str
name: Name
notes: str

CustomerDirectory = RootModel[dict[str, Customer]]

Теперь парсим файл аналогично, но уже с dataclass-объектами:

import ijson

with open("customers.json", "rb") as f:
data = {}
for cust_id, cust_dict in ijson.kvitems(f, ""):
customer = Customer(**cust_dict)
data[cust_id] = customer
directory = CustomerDirectory.model_validate(data)

✅ Результат: память падает ещё сильнее — с 1200 MB до 450 MB! Уже неплохо, правда?

🚀 Что ещё можно улучшить? Личный опыт

Хочу добавить, что помимо слотов и инкрементального парсинга есть и другие приёмы для улучшения работы с большими данными:

  • 📦 Использование генераторов (generator) вместо полного списка объектов.
  • ⚙️ Ленивое чтение данных (lazy-loading) только при необходимости обращения к ним.
  • 🗃️ Хранение промежуточных данных в базах данных, таких как SQLite, вместо оперативной памяти.

Pydantic мог бы встроить аналогичную функциональность непосредственно внутрь своей архитектуры. Например, в перспективе авторы библиотеки могли бы реализовать встроенный инкрементальный парсинг и поддержку слотов без необходимости вручную использовать dataclasses.

🎯 Заключение и выводы

Проблема с памятью при парсинге JSON в Pydantic не очевидна, пока вы не столкнётесь с реальным большим файлом. Но как только столкнётесь, эти знания сэкономят вам не только гигабайты памяти, но и массу нервов. Сам подход к оптимизации, предложенный в статье Итамара, полезен не только для конкретного кейса JSON+Pydantic, но и в принципе при работе с любыми большими объёмами данных в Python.

Для меня главные выводы таковы:

  • 💡 Всегда думайте о памяти заранее, если ожидаете большие данные.
  • 🔧 Стриминг и слоты — простые инструменты, доступные каждому Python-разработчику.
  • 🛡️ Если есть возможность, избегайте хранения всего объёма данных в памяти и используйте промежуточные хранилища и ленивое чтение.

Оптимизация — это искусство, которое позволяет писать по-настоящему производительный код даже в высокоуровневых языках вроде Python.

Не забывайте: «Память дешевле, чем раньше, но всё ещё не бесплатна!»

🔗 Полезные ссылки: