Форматы JSON и CSV в Python: чтение и запись данных

В прошлой главе мы разобрали файл настроек вручную, резая строки по знаку =. Для данных посложнее так не делают: есть готовые форматы, которые Python читает и пишет готовыми модулями.

Разберём два самых частых:

  • JSON (JavaScript Object Notation) — формат обмена данными в вебе и API
  • CSV (Comma-Separated Values) — табличный формат; открывается в Excel и Google Таблицах

JSON: JavaScript Object Notation

Что JSON умеет хранить

JSON придумывали как формат обмена между разными языками, поэтому набор типов в нём намеренно скромный, только то, что есть везде:

JSONPython
{ … }объект{ … }dict
"Anna"строка"Anna"str
28число28int
4.5число4.5float
trueлогическоеTruebool
nullnullNoneNone
[ … ]массив[ … ]list

Соответствие почти один в один: только у числа в Python два варианта — целое становится int, дробное — float. На этом и держится удобство работы с JSON из Python.

Модуль json

В стандартной библиотеке для формата есть модуль json, и вся работа с ним сводится к двум действиям: превратить объект Python в JSON и разобрать JSON обратно в объект.

У модуля четыре функции. dump и load работают с файлом: первая записывает объект Python, вторая читает его обратно. dumps и loads делают то же самое со строкой: буква s на конце как раз от слова string.

строка JSONв памяти'{"name": …}'
json.dumps
json.loads
объект Python{"name": …}
json.dump
json.load
файл JSONна дискеdata.json

Начнём со строк: так виден сам формат. У dumps есть два аргумента, которые почти всегда стоит указывать. ensure_ascii=False оставляет кириллицу кириллицей. Без него «Анна» уедет в строку как \u0410\u043d\u043d\u0430: формально верно, а читать невозможно. А indent=2 расставляет переносы и отступы, чтобы результат можно было разглядывать глазами.

Python 3.13
import json

person = {
    "name": "Анна",
    "age": 28,
    "city": "Москва",
    "languages": ["Python", "JavaScript"]
}

json_string = json.dumps(person, ensure_ascii=False, indent=2)
print(json_string)
{
  "name": "Анна",
  "age": 28,
  "city": "Москва",
  "languages": [
    "Python",
    "JavaScript"
  ]
}
parsed_data = json.loads(json_string)
print(f"Имя: {parsed_data['name']}, языки: {parsed_data['languages']}")
Имя: Анна, языки: ['Python', 'JavaScript']

Запись JSON в файл и чтение из файла

Те же данные обычно нужно не просто превратить в строку, а положить на диск. Тут в дело идёт пара без s: dump пишет объект прямо в открытый файл, load читает его оттуда. Промежуточная строка не нужна.

Python 3.13
import json

students = [
    {"id": 1, "name": "Иван", "scores": [85, 90, 78]},
    {"id": 2, "name": "Мария", "scores": [92, 88, 95]}
]

with open('students.json', 'w', encoding='utf-8') as file:
    json.dump(students, file, ensure_ascii=False, indent=2)

with open('students.json', 'r', encoding='utf-8') as file:
    loaded_students = json.load(file)

for student in loaded_students:
    print(f"  {student['name']}: оценки {student['scores']}")
  Иван: оценки [85, 90, 78]
  Мария: оценки [92, 88, 95]

CSV: Comma-Separated Values

Модуль csv

Ниже два файла: people.csv с маленькой таблицей и main.py, который её читает. Начнём с вкладки people.csv. Это основа формата: первая строка — заголовки, каждая следующая — строка таблицы, столбцы разделены запятыми.

Глядя на такой файл, легко решить, что никакой модуль не нужен: режем строку через split(","), и готово. Так и работает, но только до первой запятой внутри самого значения: она рано или поздно встретится в адресе или описании товара. Модуль csv знает правила формата и берёт эти хлопоты на себя. В main.py файл читает csv.reader:

people.csv
1Имя,Возраст,Город2Анна,28,Москва3Иван,35,Санкт-Петербург4Мария,22,Казань5

Запустим main.py — в выводе:

  Анна, 28 лет, город Москва
  Иван, 35 лет, город Санкт-Петербург
  Мария, 22 лет, город Казань

Запятая внутри значения

Вернёмся к обещанной проблеме. Что произойдёт, если запятая окажется в самом значении, например в описании «лёгкий, тонкий»? Наивный split(",") разрезал бы такое значение надвое: один столбец превратился бы в два, и вся строка поехала бы вправо.

Формат решает это кавычками: значение с запятой берётся в кавычки, и разделителем считается только запятая снаружи них. Расставлять их руками не нужно: запись таблицы в файл — работа csv.writer, и кавычки он добавит сам, а csv.reader при чтении снимет.

Файл для записи открывают с параметром newline='': так переводами строк управляет сам модуль csv, иначе на Windows между строками таблицы появились бы пустые.

Посмотрим, как это выглядит в файле:

Python 3.13
import csv

rows = [
    ['Товар', 'Описание', 'Цена'],
    ['Ноутбук', 'лёгкий, тонкий', '45000'],
]

with open('quoted.csv', 'w', newline='', encoding='utf-8') as file:
    csv.writer(file).writerows(rows)

with open('quoted.csv', 'r', encoding='utf-8') as file:
    print(file.read(), end="")
Товар,Описание,Цена
Ноутбук,"лёгкий, тонкий",45000

Кавычки вокруг «лёгкий, тонкий» появились сами, и столбцов в строке осталось три.

Кавычки — не единственная тонкость формата: несмотря на название, разделителем не всегда служит запятая. В выгрузках из русской версии Excel это чаще всего ;, потому что запятая там занята под десятичный разделитель. Разделитель задают параметром delimiter, причём одинаково при записи и при чтении:

Python 3.13
writer = csv.writer(file, delimiter=';')
reader = csv.reader(file, delimiter=';')

Иначе вся строка вернётся одним куском вместо отдельных столбцов.

Обращение к столбцам по имени

До сих пор строки приходили к нам списками, и до нужного значения мы добирались по номеру: row[0], row[1], row[2]. Пока столбцов три и файл ваш собственный, это терпимо. Но стоит кому-то добавить в выгрузку новый столбец в середину — и все номера съедут. Программа при этом не упадёт: она молча начнёт печатать город там, где ожидался возраст. Неправильный результат без ошибки найти труднее, чем падение.

От номеров избавляет DictReader. Он смотрит на строку заголовков и отдаёт каждую строку словарём, где ключ — имя столбца. Прочитаем им тот же people.csv:

main.py
1import csv2 3with open('people.csv', 'r', encoding='utf-8') as file:4  for row in csv.DictReader(file):5      print(f"  {row['Имя']}, {row['Возраст']} лет, город {row['Город']}")6 

Запустим main.py — в выводе:

  Анна, 28 лет, город Москва
  Иван, 35 лет, город Санкт-Петербург
  Мария, 22 лет, город Казань

Результат тот же, но в коде вместо номеров стоят имена столбцов, и новый столбец в файле ничего не сдвинет. Для записи словарей есть парный DictWriter: он устроен так же, только заранее просит список имён столбцов.

Проверка понимания

Чем json.dumps отличается от json.dump?