Сохраняем результаты: json.dump итоговых данных
Отфильтровали, отсортировали, посчитали — теперь результат должен пережить скрипт: json.dump пишет итог в файл с кириллицей и лесенкой.
Редакция Питоники
Два урока подряд ты обрабатывал ответ API: фильтр отобрал нужное, sorted навёл порядок. Есть одна проблема: всё это живёт в переменных и умирает вместе со скриптом. Завтра запустишь — и считать заново. Отчёты для того и существуют, чтобы переживать запуск: сегодня записываем итоги на диск.
Файл с результатами — это ещё и стык между программами: отчёт, который записал твой скрипт на Python, завтра откроет коллега на JavaScript, а послезавтра — скрипт на Go. JSON задуман как язык обмена, поэтому «сохранить результат» почти всегда означает «сохранить в json»: читают его все, типы в нём не врут, а кириллица при правильных параметрах остаётся кириллицей.
json.dump: итоговый список в файл
В уроке 4 вы уже записывали словарь в файл — здесь то же самое, только в файл едет не сырой ответ, а результат обработки. Схема одна: открыли файл на запись, json.dump(data, f, ...), закрыли — потом прочли обратно и убедились, что всё доехало.
import json, os
API_RESPONSE = """
{
"status": "ok",
"source": "shop-api",
"items": [
{"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
{"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
{"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
{"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
{"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
]
}
"""
items = json.loads(API_RESPONSE)["items"]
cheap = [item for item in items if item["price"] < 5000]
with open("cheap.json", "w", encoding="utf-8") as f:
json.dump(cheap, f, ensure_ascii=False, indent=2)
with open("cheap.json", encoding="utf-8") as f:
back = json.load(f)
print(len(back), "товара в файле")
print(back[0]["name"], "-", back[0]["price"])
os.remove("cheap.json")
3 товара в файле Клавиатура - 4990
Два параметра записи — старые знакомые: ensure_ascii=False оставляет кириллицу кириллицей вместо \u-кракозябр (разбор — в уроке 5), а indent=2 рисует лесенку, которую приятно открыть глазами (урок 6). Файл удаляем в конце, чтобы блок можно было запускать сколько угодно раз — у себя на компьютере ты его, конечно, оставишь.
dumps и dump: текст или файл
Буква s в названии — строка: json.dumps возвращает текст, json.dump сразу пишет его в открытый файл. Под капотом dump вызывает тот же dumps — разница только в адресате.
import json
data = {"name": "Мышь", "price": 1290}
text = json.dumps(data, ensure_ascii=False)
print(text)
print(type(text).__name__)
{"name": "Мышь", "price": 1290}
strСтрока от dumps годится для сообщений, логов и вставки в другой JSON; для файлов удобнее dump — не плодишь промежуточный текст. Оба параметра — ensure_ascii и indent — работают одинаково у обоих.
| Функция | Куда попадают данные | Типичный случай |
|---|---|---|
| json.dumps(data) | возвращённая строка | логи, сообщения, вложение в другой ответ |
| json.dump(data, f) | открытый файл | отчёты, выгрузки, кэш |
| json.loads(text) | из строки в Python | разбор ответа API |
| json.load(f) | из файла в Python | чтение конфига и выгрузок |
Для файлов, которые меняются от запуска к запуску и сравниваются глазами или diff-ом, есть ещё один привычный параметр — sort_keys=True: ключи выстраиваются по алфавиту, и отчёт от вторника отличается от отчёта со среды только содержимым, а не порядком ключей.
import json
report = {"source": "shop-api", "items": ["Мышь", "Коврик"], "count": 2}
print(json.dumps(report, ensure_ascii=False))
print(json.dumps(report, ensure_ascii=False, sort_keys=True))
{"source": "shop-api", "items": ["Мышь", "Коврик"], "count": 2}
{"count": 2, "items": ["Мышь", "Коврик"], "source": "shop-api"}Не голый список, а отчёт
Список товаров в файле — уже хорошо, но через неделю никто не вспомнит, что это и когда посчитано. Заворачивай итог в словарь с метаданными: кто считал, сколько, по какому условию. Данные и справка о них поедут вместе.
import json, os
API_RESPONSE = """
{
"status": "ok",
"source": "shop-api",
"items": [
{"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
{"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
{"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
{"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
{"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
]
}
"""
items = json.loads(API_RESPONSE)["items"]
cheap = [item for item in items if item["price"] < 5000]
report = {
"generated_by": "shop-filter",
"condition": "price < 5000",
"count": len(cheap),
"items": cheap,
}
with open("report.json", "w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
with open("report.json", encoding="utf-8") as f:
back = json.load(f)
print(sorted(back.keys()))
print("Товаров:", back["count"])
os.remove("report.json")
['condition', 'count', 'generated_by', 'items'] Товаров: 3
Ключ condition — настоящая находка для будущего тебя: файл сам рассказывает, по какому правилу собран. Такие метаданные ничего не стоят при записи и экономят часы при разборе чужих выгрузок. Заметь и форму: condition — строка с условием на человеческом языке, а не программа. Файл читает не только Python, и справка, понятная без интерпретатора, ценнее самой хитрой структуры.
Про indent=2 одна оговорка: лесенка раздувает файл — на десятках мегабайт данных отступы съедают заметную часть объёма. Поэтому правило простое: людям — indent, машинам — без. Отчёт для глаза и diff-а пишется с indent=2, а промежуточная передача между программами — сплошной строкой; json.loads читает оба вида одинаково.
Перезапись или дополнение
У json.dump нет режима «дописать». Каждый вызов json.dump пишет файл с нуля: старое содержимое исчезает, остаётся последняя версия данных. Это не баг, а правило формата: JSON-файл — одна целая структура, и половинки в нём не живут. Если накопить нужно — читай старый файл, изменяй данные в памяти и записывай всё обратно.
import json, os
PATH = "stats.json"
stats = {}
if os.path.exists(PATH):
with open(PATH, encoding="utf-8") as f:
stats = json.load(f)
stats["мыши"] = stats.get("мыши", 0) + 2
stats["клавиатуры"] = stats.get("клавиатуры", 0) + 1
with open(PATH, "w", encoding="utf-8") as f:
json.dump(stats, f, ensure_ascii=False, indent=2)
print(stats)
os.remove(PATH)
{'мыши': 2, 'клавиатуры': 1}Проверка os.path.exists отличает первый запуск от последующих: файла ещё нет — начинаем с пустого словаря. stats.get(ключ, 0) подставляет ноль для товара, который раньше не встречался. Запусти такой код у себя два раза подряд — во второй прогон статистика придёт уже накопленная: три мыши и две клавиатуры. В песочнице файл удаляется, чтобы каждый запуск был честным первым — накопление легко проверить и так, читая файл между прогонами.
import json, os
PATH = "latest.json"
with open(PATH, "w", encoding="utf-8") as f:
json.dump({"run": "первая"}, f, ensure_ascii=False)
with open(PATH, "w", encoding="utf-8") as f:
json.dump({"run": "вторая"}, f, ensure_ascii=False)
with open(PATH, encoding="utf-8") as f:
print(json.load(f))
os.remove(PATH)
{'run': 'вторая'}Первый dump не пережил второй: файл хранит то, что записано последним. Для отчётов это как раз удобно — имя файла стабильное, а содержимое всегда актуальное; исторические версии лучше складывать в файлы с датой в имени. Скрипт всегда можно запустить заново — данные пересоберутся и перезапишутся без страхов и бэкапов.
Проверка: прочитай обратно и сравни
Скрипт, который молча записал файл, ещё ничего не доказал: диск мог подвести, путь — оказаться не тем, кодировка — сработать против тебя. Дешёвая страховка — round-trip: сразу после записи прочитай файл json.load и сравни с тем, что собирался сохранить.
import json, os
API_RESPONSE = """
{
"status": "ok",
"source": "shop-api",
"items": [
{"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
{"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
{"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
{"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
{"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
]
}
"""
items = json.loads(API_RESPONSE)["items"]
result = sorted(
(item for item in items if item["in_stock"]),
key=lambda item: item["price"],
)
PATH = "available.json"
with open(PATH, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
with open(PATH, encoding="utf-8") as f:
back = json.load(f)
print("Совпало:", back == result)
print("В файле:", len(back), "товара")
os.remove(PATH)
Совпало: True В файле: 3 товара
back == result сравнивает структуры по содержимому — порядок ключей внутри словарей не важен, важны значения. True значит: файл корректен и в нём ровно то, что ты хотел сохранить. Одна строка проверки — и отчёт перестаёт быть обещанием.
В файл можно уложить и сводку целиком — итог всех вопросов из урока 15: сколько всего, сколько в наличии, какая средняя. Числа в словаре едут как числа: json.dump сохраняет типы, и после обратного чтения avg_price можно снова делить и округлять без единого преобразования.
import json, os
API_RESPONSE = """
{
"status": "ok",
"source": "shop-api",
"items": [
{"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
{"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
{"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
{"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
{"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
]
}
"""
items = json.loads(API_RESPONSE)["items"]
summary = {
"source": "shop-api",
"count": len(items),
"in_stock": len([i for i in items if i["in_stock"]]),
"avg_price": round(sum(i["price"] for i in items) / len(items), 2),
}
PATH = "summary.json"
with open(PATH, "w", encoding="utf-8") as f:
json.dump(summary, f, ensure_ascii=False, indent=2)
with open(PATH, encoding="utf-8") as f:
print(json.load(f))
os.remove(PATH)
{'source': 'shop-api', 'count': 5, 'in_stock': 3, 'avg_price': 6896.0}Имя файла — тоже данные
result.json через неделю расскажет не больше, чем «какой-то результат». Имя — бесплатное место для метаданных, и в проектах его используют на полную: что за данные, какая выборка, когда собраны.
- cheap-under-5000.json — что внутри и по какому условию;
- catalog-fantasy.json — к какой категории относится;
- report-2026-10-06.json — дата сборки: история версий сама себя ведёт;
- items-shop-api.json — из какого источника приехали данные.
Дату в имя обычно ставит strftime из модуля datetime при каждом запуске — и папка отчётов превращается в архив по дням без единой лишней руки. А вот пробелы и кириллицу в именах лучше не пускать: они законны, но усложняют жизнь командной строке и чужим скриптам. Строчные латинские буквы, цифры, дефис — скучно и надёжно.
Чек-лист сохранения результата
- открытие файла: режим "w" и encoding="utf-8" — обе части обязательны;
- запись: json.dump(data, f, ensure_ascii=False, indent=2);
- итог — словарь с метаданными: кто, когда, по какому условию считал;
- проверка: json.load обратно и сравнение с исходными данными.
import json, os
API_RESPONSE = """
{
"status": "ok",
"source": "shop-api",
"items": [
{"name": "Клавиатура", "price": 4990, "rating": 4.7, "in_stock": true},
{"name": "Мышь", "price": 1290, "rating": 4.3, "in_stock": true},
{"name": "Монитор", "price": 18400, "rating": 4.8, "in_stock": false},
{"name": "Наушники", "price": 7350, "rating": 3.9, "in_stock": true},
{"name": "Веб-камера", "price": 2450, "rating": 4.1, "in_stock": false}
]
}
"""
items = json.loads(API_RESPONSE)["items"]
cheap = sorted(
(item for item in items if item["price"] < 5000),
key=lambda item: item["price"],
)
export = [{"name": i["name"], "price": i["price"]} for i in cheap]
PATH = "export.json"
with open(PATH, "w", encoding="utf-8") as f:
json.dump(export, f, ensure_ascii=False, indent=2)
with open(PATH, encoding="utf-8") as f:
back = json.load(f)
print(back == export, len(back))
print(back[0])
os.remove(PATH)
True 3
{'name': 'Мышь', 'price': 1290}Перед сохранением из товаров вырезаны лишние поля — получателю не нужен рейтинг и флаги, файл стал легче и понятнее. Формировать итоговую структуру под задачу получателя — такая же часть обработки, как фильтрация.
Что дальше
Результат обработки теперь переживает скрипт: отчёт с метаданными, кириллица без кракозябр, лесенка для человека и обратное чтение для уверенности. Но у JSON есть конкурент для табличных данных — CSV, родной формат Excel. Следующий урок сравнит оба формата и научит переводить данные туда и обратно.
Сохранённый отчёт — это данные плюс справка о себе: что, сколько, по какому условию и проверено ли обратным чтением.
Сначала предскажи ответ в голове — это главный навык программиста.
import json
data = {"price": 1290}
text = json.dumps(data)
print(text, type(text).__name__)
import json
saved = '{"n": 1}'
data = json.loads(saved)
data["n"] += 4
print(data)
import json
PATH = "stat.json"
with open(PATH, "w", encoding="utf-8") as f:
json.dump({"clicks": 10}, f)
with open(PATH, "w", encoding="utf-8") as f:
json.dump({"clicks": 25}, f)
with open(PATH, encoding="utf-8") as f:
print(json.load(f)["clicks"])
1. Чем json.dump отличается от json.dumps?
2. Что станет с существующим файлом после open(PATH, "w") и json.dump?
3. Как правильно «дополнить» json-файл новыми данными?
4. Зачем после записи читать файл обратно и сравнивать с исходными данными?
5. Почему для записи отчёта с кириллицей нужны и ensure_ascii=False, и encoding="utf-8"?
6. Что даёт заворачивание итогового списка в словарь с ключами generated_by и count?
Собери выгрузку «склад»: отфильтруй товары в наличии, отсортируй их по цене по возрастанию и сохрани список в файл stock.json с ensure_ascii=False и indent=2. Прочитай файл обратно, выведи результат сравнения с исходным списком и количество товаров в файле.
Как сохранить обработанные данные в json-файл в Python?
Открыть файл с режимом "w" и encoding="utf-8", вызвать json.dump(data, f, ensure_ascii=False, indent=2). ensure_ascii=False оставляет кириллицу читаемой, indent=2 делает файл удобным для человека. Затем прочитать файл json.load и сравнить с исходными данными — это проверка записи.
Почему нельзя дописывать в json-файл режимом «a»?
JSON-файл — одна целая структура: дописанный в конец текст ломает скобки, и первое же чтение падает с JSONDecodeError. Дополнение делают через чтение: json.load старого файла, обновление данных в памяти, json.dump всего обратно с "w".
Как проверить, что файл json записался правильно?
Round-trip: сразу после записи открыть файл и прочитать json.load, затем сравнить с исходной структурой: back == result. Сравнение проверяет содержимое, а не порядок ключей. Одна строка экономит часы поиска «пустого» или битого файла.
Чем json.dump лучше обычной записи файла через f.write?
f.write принимает только строки: словари и числа придётся вручную превращать в текст и экранировать. json.dump делает это сам и гарантирует корректный JSON: типы сохраняются, кириллица не ломается при ensure_ascii=False, а indent рисует лесенку.
Один большой json-файл или много маленьких?
Зависит от того, как данные читаются дальше. Один отчёт — один файл: доставка целиком, метаданные внутри, обратное чтение одной строкой. Разные сущности — разные файлы: каталог книг и статистика заказов живут своей жизнью и перезаписываются независимо. Когда отчёт вырастает до десятков мегабайт, его делят по датам или категориям в имена файлов — путь уже описан выше.
Как сохранить в один файл несколько разных списков?
Завернуть их в общий словарь: {"cheap": cheap, "available": available, "summary": summary} — и сделать один json.dump. JSON-файл хранит одну структуру, но у структуры может быть сколько угодно ключей; обратное чтение вернёт все списки по их именам. Отдельные json.dump в один файл не помогут — второй вызов перезапишет первый.
Понравился урок? Сошлитесь на него
«Каждый вызов json.dump пишет файл с нуля: старое содержимое исчезает, остаётся последняя версия данных.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
json · Урок 4
Файлы JSON: json.dump и json.load
Данные, которые переживают скрипт: json.dump пишет словарь в файл, json.load читает обратно, а round-trip подтверждает — сохранил, прочитал, совпало.
json · Урок 16
Сортировка и топ-N: sorted по ключу
Список из API приходит в том порядке, в каком отдал сервер: sorted с key наводит порядок, reverse разворачивает, срез берёт топ-3.
json · Урок 18
CSV и JSON: две таблицы, два формата
У таблиц два языка: CSV для Excel и плоских выгрузок, JSON для типов и вложенности. Модуль csv переводит с обоих.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
BeautifulSoup / Scrapy · Урок 7
Сохранение результатов: CSV, JSON и базы данных
Список словарей собран — теперь доведём его до файла: CSV для таблиц, JSON для вложенных структур, sqlite3 для больших объёмов. Плюс две классические ловушки: пустые строки и кракозябры в Excel.
сохранение данных pythonкак сохранить результаты парсинга в csv json
pytest · Урок 17
Тесты для JSON и файлов: tmp_path на практике
Сохранить заказы в JSON, прочитать обратно и не разочароваться: round-trip тесты, кириллица с encoding utf-8, битые файлы через pytest.raises и tmp_path — папка, которая достаётся каждому тесту своя.
тестирование json pythonround trip тест json
json · Урок 20
Проект: каталог книг — от URL до файла
Финал раздела: один скрипт собирает каталог книг — параметры в URL, разбор ответа, фильтр по рейтингу, сортировка по цене и проверенный файл на диске.
python проект json api каталогот url до файла python