Файлы и форматы данных: текст, CSV, JSON
У всех наших программ до сих пор была одна общая беда: они забывали всё. Вы запускали программу, она считала список покупок, красиво выводила его на экран — а потом завершалась, и от результата не оставалось следа. Запустили заново — начинай сначала.
Это не недоработка Python. Так устроен компьютер. В статье Как устроен компьютер мы говорили о двух видах памяти: быстрой оперативной (RAM), которая очищается при выключении, и медленном, но долговечном диске. Все переменные из статьи Переменные и типы данных живут в оперативной памяти. Чтобы данные пережили закрытие программы, их нужно записать на диск — то есть в файл.
Этим мы сегодня и займёмся. И заодно разберёмся, в каком виде данные в файл класть: просто текстом, таблицей CSV или структурой JSON.
shopping = ['хлеб', 'молоко']"] end subgraph DISK["Диск — медленнее, но помнит"] B["файл shopping.txt"] end A -- "запись в файл" --> B B -- "чтение из файла" --> A A -.-> C["программа закрылась —
данные исчезли"] B -.-> D["программа закрылась —
файл на месте"]
Что такое файл на самом деле
Файл — это именованная последовательность байтов на диске. Всё. Никакой магии.
Байт, как мы помним, — это число от 0 до 255. Значит, любой файл — просто длинный ряд чисел. А чем эти числа считать, решает программа, которая файл открывает:
- если числа договорились считать кодами символов — получится текстовый файл;
- если числа означают яркость точек — получится картинка;
- если это инструкции процессору — получится программа.
Расширение (.txt, .jpg, .py) — это подсказка человеку и операционной системе, а не свойство самого файла. Переименовав фото.jpg в фото.txt, вы не превратите картинку в текст: байты останутся теми же, просто блокнот попытается прочитать их как символы и покажет мусор.
Аналогия. Файл — это папка-скоросшиватель в архиве: у неё есть имя на корешке, внутри лежат страницы подряд, и её можно открыть, дочитать, дописать в конец, закрыть.
Где аналогия ломается. В бумажной папке вы можете вставить лист в середину, чуть подвинув остальные. В файле так нельзя: чтобы вставить строку в середину, нужно перезаписать весь файл целиком. Это одна из главных причин, почему программисты чаще читают файл полностью, меняют данные в памяти и пишут обратно — а не «правят на месте».
Текстовые и двоичные файлы
Все файлы делятся на две группы.
Текстовые — те, которые человек может открыть в блокноте и прочитать: .txt, .py, .csv, .json, .html. Внутри — символы, разбитые на строки.
Двоичные (binary) — все остальные: .jpg, .mp3, .zip, .exe. Открывать их блокнотом бессмысленно.
Мы будем работать только с текстовыми — с них начинают все, и на них устроено большинство форматов обмена данными.
Ключевая деталь на картинке: перевод строки — это обычный символ, просто невидимый. В коде он записывается как \n (обратный слэш и буква n). Когда вы жмёте Enter в блокноте, в файл добавляется именно он. Забыв про \n, новички получают склеенный текст в одну строку — мы это ещё увидим.
Читаем файл: open и with
Работа с файлом всегда идёт по одной схеме: открыть → прочитать или записать → закрыть. Закрывать обязательно: пока файл открыт, операционная система держит его «занятым», а записанные данные могут ещё не долететь до диска.
Чтобы не забывать про закрытие, в Python есть конструкция with. Она закрывает файл автоматически — даже если внутри произошла ошибка.
Сначала создадим файл, с которым будем работать. Запустите:
# "w" означает write — записать (создать заново)
with open("shopping.txt", "w", encoding="utf-8") as f:
f.write("хлеб\n")
f.write("молоко\n")
f.write("яйца\n")
print("Файл записан")
Разберём построчно:
open("shopping.txt", "w", encoding="utf-8")— открываем файл. Первый аргумент — имя, второй — режим ("w"= запись), третий — кодировка (о ней ниже).as f— результат кладём в переменнуюf. Это не сам текст, а «пульт управления файлом», по-научному файловый объект.- Отступ внутри
with— блок, где файл открыт. Как только блок кончился, файл закрыт. f.write("хлеб\n")— записывает строку.writeне добавляет перевод строки сам, поэтому\nмы пишем руками.
Ожидаемый вывод: Файл записан. Рядом с вашим .py-файлом появится shopping.txt. Откройте его блокнотом — там три строки.
Теперь прочитаем его тремя способами.
Способ 1: всё целиком в одну строку
with open("shopping.txt", encoding="utf-8") as f:
content = f.read()
print(content)
print("Длина:", len(content))
хлеб
молоко
яйца
Длина: 17
Обратите внимание: режим мы не указали — по умолчанию это "r" (read, чтение). А длина 17, а не 15: три символа \n тоже считаются.
read() удобен для маленьких файлов. Для файла на 10 гигабайт он попытается уместить всё в оперативную память — и программа упадёт.
Способ 2: построчно (так делают чаще всего)
with open("shopping.txt", encoding="utf-8") as f:
for line in f:
print(repr(line))
'хлеб\n'
'молоко\n'
'яйца\n'
Файловый объект можно перебирать циклом for (см. Циклы) — на каждом шаге он отдаёт одну строку. Файл читается кусочками, поэтому размер не важен.
repr(...) — команда «покажи значение так, как оно выглядит в коде». Я использовал её нарочно, чтобы стал виден \n на конце. Это и есть главная ловушка чтения файлов.
Способ 3: построчно и без \n
with open("shopping.txt", encoding="utf-8") as f:
for number, line in enumerate(f, start=1):
print(number, line.strip())
1 хлеб
2 молоко
3 яйца
line.strip() убирает пробелы и переводы строк с начала и конца. Правило простое: прочитали строку из файла — сразу .strip(), иначе "хлеб\n" == "хлеб" даст False, и вы полчаса будете искать, почему сравнение не работает.
enumerate(f, start=1) — помощник, который выдаёт пару «номер, значение», нумеруя с единицы.
Если нужен сразу список всех строк:
with open("shopping.txt", encoding="utf-8") as f:
lines = [line.strip() for line in f]
print(lines) # ['хлеб', 'молоко', 'яйца']
Режимы открытия: w стирает, a дописывает
| Режим | Что делает | Если файла нет | Если файл есть |
|---|---|---|---|
"r" |
чтение (по умолчанию) | ошибка FileNotFoundError |
читает |
"w" |
запись | создаёт | стирает содержимое и пишет заново |
"a" |
дозапись (append) | создаёт | добавляет в конец |
"x" |
создание | создаёт | ошибка — файл уже есть |
Запомните строчку про "w": она молча уничтожает старое содержимое. Ни вопроса, ни предупреждения. Это самая дорогая ошибка новичка в этой теме — открыл файл с данными в режиме "w", чтобы «дописать пару строк», и потерял всё.
# правильный способ дописать
with open("shopping.txt", "a", encoding="utf-8") as f:
f.write("кофе\n")
with open("shopping.txt", encoding="utf-8") as f:
print(f.read())
хлеб
молоко
яйца
кофе
Кодировка: почему encoding="utf-8" нужно писать всегда
Компьютер хранит числа, а мы хотим буквы. Кодировка — это таблица соответствия «число ↔ символ». Разные таблицы дают разные буквы для одного и того же байта.
Аналогия. Кодировка — как язык, на котором написана записка. Одна и та же последовательность букв «cola» по-русски и по-испански читается одинаково, а вот «pan» означает хлеб в Испании и сковороду в Швеции. Прочитать текст «не тем языком» — получить бессмыслицу.
Сегодня стандарт — UTF-8: в нём есть все алфавиты мира, эмодзи и почти всё остальное. Но в Windows у Python исторически может быть другая кодировка по умолчанию, поэтому один и тот же код работает у вас и ломается у коллеги. Лечится это одной привычкой:
Всегда пишите
encoding="utf-8"при открытии текстового файла. Всегда.
Если кодировка не та, вы увидите:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xcf in position 0: invalid continuation byte
Дословно: «кодек utf-8 не может расшифровать байт 0xcf на позиции 0». Значит, файл записан в другой кодировке — чаще всего это старая русская cp1251 (она же Windows-1251). Попробуйте encoding="cp1251".
Пути к файлам: где программа ищет shopping.txt
Имя "shopping.txt" — это относительный путь: файл ищется в текущей рабочей директории, то есть в той папке, откуда программу запустили. Не там, где лежит .py-файл, а именно откуда запустили — это разные вещи, и на этом спотыкаются все.
Абсолютный путь начинается от корня диска и не зависит ни от чего: /home/anna/data/shopping.txt в Linux и macOS, C:\Users\Anna\data\shopping.txt в Windows.
В Windows обратный слэш в Python-строке — спецсимвол, поэтому пишите либо "C:\\Users\\Anna" (двойной), либо r"C:\Users\Anna" (буква r перед кавычкой отключает спецсимволы).
Удобнее пользоваться модулем pathlib, который сам разбирается с различиями систем:
from pathlib import Path
p = Path("shopping.txt")
print(p.exists()) # True — файл существует?
print(p.suffix) # .txt — расширение
# прочитать файл целиком одной командой
print(Path("shopping.txt").read_text(encoding="utf-8").splitlines())
# ['хлеб', 'молоко', 'яйца', 'кофе']
splitlines() разрезает текст по переводам строк и сразу отдаёт список без \n. Документация: docs.python.org/3/library/pathlib.html.
Три типичные ошибки и что они означают дословно
FileNotFoundError: [Errno 2] No such file or directory: 'shoping.txt'
«Нет такого файла или каталога». Проверьте: опечатку в имени (shoping вместо shopping), из какой папки запускаете программу, не забыли ли расширение. В 90 % случаев это опечатка или неверная рабочая директория.
PermissionError: [Errno 13] Permission denied: 'report.csv'
«Доступ запрещён». Обычно файл открыт в Excel — Windows не даёт писать в занятый файл. Закройте программу, которая его держит.
IsADirectoryError: [Errno 21] Is a directory: 'data'
«Это каталог». Вы пытаетесь открыть папку как файл — вероятно, забыли дописать имя файла к пути.
Простой текст — это мало. Знакомьтесь: CSV
Пока мы хранили список слов, простого текста хватало. Но данные редко бывают такими плоскими. Допустим, вы записываете расходы: дата, категория, сумма, комментарий. Как уместить четыре значения в одну строку файла?
Ответ очевиден: разделить их каким-нибудь символом. Именно это и делает формат CSV — Comma-Separated Values, «значения, разделённые запятыми».
CSV — это по-прежнему обычный текстовый файл. Его открывает блокнот. Его же открывает Excel, Google Sheets и любая программа для аналитики — поэтому CSV стал универсальным языком обмена таблицами.
Создадим файл purchases.csv со строкой заголовка и пятью записями:
import csv
rows = [
["дата", "категория", "сумма", "комментарий"],
["2026-03-01", "еда", "540.50", "обед"],
["2026-03-02", "транспорт", "60", "метро"],
["2026-03-03", "еда", "1200.00", "кафе, с другом"],
["2026-03-04", "развлечения", "900", "кино"],
["2026-03-05", "еда", "310", "продукты"],
]
with open("purchases.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows)
print("Готово")
import csv— подключаем встроенный модуль для работы с CSV. Он идёт в комплекте с Python, устанавливать ничего не надо.newline=""— обязательный аргумент именно при работе с csv. Без него в Windows между строками появятся лишние пустые строки. Правило: файл подcsvоткрываем сnewline="".csv.writer(f)— создаёт «писателя», который умеет превращать списки в строки CSV.writerows(rows)— записывает сразу все строки.
Содержимое получившегося файла:
дата,категория,сумма,комментарий
2026-03-01,еда,540.50,обед
2026-03-02,транспорт,60,метро
2026-03-03,еда,1200.00,"кафе, с другом"
2026-03-04,развлечения,900,кино
2026-03-05,еда,310,продукты
Заметили кавычки вокруг "кафе, с другом"? Модуль сам увидел запятую внутри значения и защитил её кавычками, чтобы поле не развалилось на два. Это и есть главная причина не парсить CSV вручную через line.split(","): ваш самодельный разбор сломается на первом же комментарии с запятой.
Чтение CSV: csv.reader и csv.DictReader
import csv
with open("purchases.csv", encoding="utf-8", newline="") as f:
reader = csv.reader(f)
for row in reader:
print(row)
['дата', 'категория', 'сумма', 'комментарий']
['2026-03-01', 'еда', '540.50', 'обед']
['2026-03-02', 'транспорт', '60', 'метро']
['2026-03-03', 'еда', '1200.00', 'кафе, с другом']
['2026-03-04', 'развлечения', '900', 'кино']
['2026-03-05', 'еда', '310', 'продукты']
Каждая строка превратилась в список. Кавычки убраны, запятая внутри поля сохранена — модуль всё сделал правильно.
Но обращаться к данным как row[2] неудобно и опасно: добавят столбец в начало — и все номера съедут. Гораздо лучше csv.DictReader: он берёт первую строку как имена столбцов и отдаёт каждую запись словарём (словари мы разбирали в статье Списки, словари и множества).
import csv
with open("purchases.csv", encoding="utf-8", newline="") as f:
for row in csv.DictReader(f):
print(row["дата"], row["категория"], row["сумма"])
2026-03-01 еда 540.50
2026-03-02 транспорт 60
2026-03-03 еда 1200.00
2026-03-04 развлечения 900
2026-03-05 еда 310
Самая частая ошибка с CSV: всё приходит текстом
row["сумма"] + 100
TypeError: can only concatenate str (not "int") to str
Дословно: «строку можно склеивать только со строкой, а не с целым числом». CSV — текстовый формат, он не хранит типы. Число 540.50 в файле — это четыре символа плюс точка, а не дробное число. Преобразуйте явно: float(row["сумма"]) или int(row["сумма"]).
Посчитаем расходы по категориям:
import csv
totals = {} # пустой словарь: категория -> сумма
with open("purchases.csv", encoding="utf-8", newline="") as f:
for row in csv.DictReader(f):
category = row["категория"]
amount = float(row["сумма"]) # текст -> число
totals[category] = totals.get(category, 0) + amount
for category, total in totals.items():
print(f"{category}: {total:.2f}")
еда: 2050.50
транспорт: 60.00
развлечения: 900.00
totals.get(category, 0) — «дай значение по ключу, а если ключа ещё нет, дай 0». Без этого на первой же новой категории программа упала бы с KeyError.
{total:.2f} внутри f-строки означает «выведи число с двумя знаками после запятой».
Запись CSV из словарей
import csv
report = [
{"категория": "еда", "потрачено": 2050.5},
{"категория": "транспорт", "потрачено": 60.0},
]
with open("report.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["категория", "потрачено"])
writer.writeheader() # пишет строку заголовка
writer.writerows(report)
fieldnames задаёт и имена столбцов, и их порядок. Если в словаре окажется ключ, которого нет в fieldnames, Python сообщит об этом ошибкой — и это хорошо: лучше упасть, чем молча потерять столбец.
Когда таблицы мало: JSON
CSV прекрасен, пока данные — плоская таблица. Но попробуйте уместить в таблицу такое: у пользователя есть имя, возраст, список хобби и вложенный адрес с городом и улицей. Сколько столбцов отвести под хобби? Три? А если их пять?
Для вложенных данных используют JSON — JavaScript Object Notation. Несмотря на название, к JavaScript вы привязаны не будете: JSON понимают все языки, и это сегодня главный формат обмена данными между программами. Когда мобильное приложение спрашивает у сервера погоду (см. Как работают интернет и сайты), ответ почти наверняка приходит в JSON.
Выглядит он так:
{
"имя": "Аня",
"возраст": 29,
"любит_кофе": true,
"хобби": ["бег", "фото"],
"адрес": null
}
Если вам это напомнило словарь Python — вы правы, сходство почти полное. JSON строится из тех же кирпичиков:
пары ключ-значение"] --> A["имя: строка «Аня»"] ROOT --> B["возраст: число 29"] ROOT --> C["любит_кофе: true / false"] ROOT --> D["адрес: null (ничего нет)"] ROOT --> E["хобби: массив [ ]"] E --> E1["«бег»"] E --> E2["«фото»"] E --> F["внутри массива
снова может быть объект —
вложенность любой глубины"]
Соответствие типов Python и JSON:
| Python | JSON | Примечание |
|---|---|---|
dict |
объект {} |
ключи в JSON — только строки |
list |
массив [] |
|
str |
строка | только двойные кавычки |
int, float |
число | |
True / False |
true / false |
с маленькой буквы |
None |
null |
Три отличия от Python, из-за которых чаще всего ломается JSON, написанный руками: только двойные кавычки, true/false/null с маленькой буквы, и запятая после последнего элемента запрещена.
Записываем и читаем JSON
import json
user = {
"имя": "Аня",
"возраст": 29,
"любит_кофе": True,
"хобби": ["бег", "фото"],
"адрес": None,
}
with open("user.json", "w", encoding="utf-8") as f:
json.dump(user, f, ensure_ascii=False, indent=2)
print("Сохранено")
json.dump(данные, файл, ...)— записать структуру в файл.ensure_ascii=False— важно для русского языка. Без него Python запишет кириллицу кодами видаимя. Файл останется правильным JSON, но человек его не прочитает.indent=2— красивое форматирование с отступом в 2 пробела. Без него всё склеится в одну длинную строку (что экономит место, но нечитаемо).
Файл user.json:
{
"имя": "Аня",
"возраст": 29,
"любит_кофе": true,
"хобби": [
"бег",
"фото"
],
"адрес": null
}
Обратное чтение — одна команда:
import json
with open("user.json", encoding="utf-8") as f:
data = json.load(f)
print(type(data)) # <class 'dict'>
print(data["имя"]) # Аня
print(data["возраст"] + 1) # 30 — это настоящее число!
print(data["хобби"][0]) # бег
Главное преимущество перед CSV: data["возраст"] — это число, а не текст. JSON сохраняет типы, преобразовывать ничего не надо.
Четыре команды модуля легко перепутать, вот шпаргалка:
| Команда | Что делает |
|---|---|
json.dump(данные, файл) |
Python → файл |
json.load(файл) |
файл → Python |
json.dumps(данные) |
Python → строка (буква s = string) |
json.loads(строка) |
строка → Python |
Версии со s нужны, когда JSON приходит не из файла, а, например, из интернета.
Ошибки JSON дословно
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
«Ожидалось имя свойства в двойных кавычках, строка 1, столбец 2». Классика: в JSON использованы одинарные кавычки {'имя': 'Аня'}. Замените на двойные.
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
«Ожидалось значение в самом начале». Обычно файл пустой или вы читаете не то, что думаете.
TypeError: Object of type set is not JSON serializable
«Объект типа множество нельзя превратить в JSON». В JSON нет множеств, дат и ваших собственных объектов — только шесть типов из таблицы выше. Множество превращают в список: list(мое_множество), дату — в строку.
Как выбрать формат
списки внутри записей,
объекты внутри объектов?"} Q1 -- "да" --> JSON["JSON
сохраняет структуру и типы"] Q1 -- "нет, плоская таблица" --> Q2{"Нужно открывать
в Excel или отдавать
не-программисту?"} Q2 -- "да" --> CSV["CSV
универсальный табличный формат"] Q2 -- "нет" --> Q3{"Это просто список
строк или журнал
событий?"} Q3 -- "да" --> TXT["Простой текст
проще некуда"] Q3 -- "нет" --> Q4{"Данных много,
нужен поиск и
одновременный доступ?"} Q4 -- "да" --> DB["Пора переходить
на базу данных"] Q4 -- "нет" --> JSON
Коротко о границах применимости. Файлы — отличное решение для настроек, отчётов, обмена данными и небольших объёмов. Но когда данных становятся миллионы записей, когда с ними работают несколько пользователей одновременно или нужен быстрый поиск по любому полю — файлы упираются в потолок, и на сцену выходят базы данных. Это отдельная большая тема.
Стоит знать ещё пару имён, которые вы встретите: XML — старший родственник JSON, многословнее, до сих пор живёт в корпоративных системах; YAML — формат для файлов конфигурации, читается как обычный текст с отступами; Parquet — двоичный формат для больших аналитических данных.
Собираем всё вместе: отчёт из CSV в JSON
Типичная задача первой недели любого начинающего разработчика: взять таблицу, посчитать по ней сводку, отдать результат в структурированном виде.
сырые записи"] --> B["прочитать
DictReader"] B --> C["преобразовать типы
текст -> число"] C --> D["посчитать суммы
по категориям"] D --> E["собрать словарь
с итогами"] E --> F["report.json
готовый отчёт"]
import csv
import json
totals = {}
# 1. Читаем таблицу и считаем суммы по категориям
with open("purchases.csv", encoding="utf-8", newline="") as f:
for row in csv.DictReader(f):
category = row["категория"]
amount = float(row["сумма"])
totals[category] = round(totals.get(category, 0) + amount, 2)
# 2. Собираем отчёт: общая сумма плюс разбивка
result = {
"всего": round(sum(totals.values()), 2),
"по_категориям": totals,
}
# 3. Сохраняем в JSON
with open("report.json", "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print("Отчёт готов:", result["всего"])
Вывод в консоль: Отчёт готов: 3010.5, а в файле report.json:
{
"всего": 3010.5,
"по_категориям": {
"еда": 2050.5,
"транспорт": 60.0,
"развлечения": 900.0
}
}
Двадцать строк кода — и вы сделали то, за что в реальной работе платят деньги. round(..., 2) здесь не для красоты: дробные числа в компьютере хранятся приближённо, и без округления вы рискуете увидеть в отчёте 2050.4999999999995. Для денег в серьёзных проектах используют специальный тип decimal.Decimal, но это тема для другого раза.
Правила, которые спасут вам нервы
- Всегда
with. Никакихf = open(...)без закрытия. - Всегда
encoding="utf-8". Одна привычка, ноль проблем с кириллицей. - Помните про
"w". Он стирает файл молча. Дописывать — только"a". .strip()после чтения строки. Невидимый\n— источник половины загадочных багов.- CSV — только через модуль
csv, и всегда сnewline="". - CSV возвращает текст. Хотите считать — преобразуйте через
int()илиfloat(). - JSON с русским — только с
ensure_ascii=False. - Не правьте важные файлы «на месте». Прочитали, изменили в памяти, записали в новый файл, потом заменили старый. Если программа упадёт посередине, у вас останется целый оригинал.
Практика
Задания идут от простого к сложному. Данные для них — файл purchases.csv из статьи; если его нет, создайте кодом из раздела про CSV.
1. Личный дневник. Напишите программу, которая спрашивает у пользователя одну строку (input()) и дописывает её в файл diary.txt вместе с переводом строки. Запустите три раза подряд и убедитесь, что все три записи на месте.
Подсказка: режим "a", и не забудьте \n в конце.
2. Считаем строки и слова. Прочитайте diary.txt и выведите: сколько в нём строк и сколько всего слов.
Подсказка: для слов пригодится line.split() — без аргументов он режет строку по пробелам и возвращает список.
3. Фильтр по категории. Прочитайте purchases.csv и создайте food.csv, куда попадут только записи с категорией «еда» — вместе со строкой заголовка.
Подсказка: csv.DictReader для чтения, csv.DictWriter с fieldnames=reader.fieldnames для записи. У объекта DictReader есть свойство .fieldnames — это как раз список имён столбцов.
4. Самая дорогая покупка. Найдите в purchases.csv запись с наибольшей суммой и выведите её дату, категорию и сумму.
Подсказка: заведите переменную most_expensive = None, идите циклом по строкам, сравнивайте float(row["сумма"]) с текущим максимумом. Сравнивать текст нельзя: "900" > "1200.00" даст True, потому что строки сравниваются посимвольно, а 9 больше 1.
5. Счётчик запусков (со звёздочкой). Сделайте программу, которая при каждом запуске увеличивает счётчик на единицу и печатает: «Вы запустили меня N раз». Счётчик храните в state.json.
Подсказка: при первом запуске файла ещё нет — оберните чтение в try / except FileNotFoundError и в этом случае начинайте с нуля. Конструкцию try подробно разберём в следующей статье, а пока используйте её как готовый рецепт:
try:
with open("state.json", encoding="utf-8") as f:
state = json.load(f)
except FileNotFoundError:
state = {"запусков": 0}
Мини-итог
Файл — это последовательность байтов на диске, и он позволяет данным пережить закрытие программы. Текстовые файлы читают и пишут через with open(...), обязательно указывая encoding="utf-8", и помнят, что режим "w" стирает содержимое, а строки приходят с \n на конце.
Когда данные становятся таблицей — берут CSV и работают с ним через модуль csv, помня, что все значения приходят текстом. Когда данные вложенные — берут JSON, который сохраняет и структуру, и типы, и требует ensure_ascii=False для кириллицы.
Дальше — официальная документация, она короткая и полезная: чтение и запись файлов, модуль csv, модуль json. Спецификация формата JSON на одной странице: json.org/json-ru.html.
Что дальше
Вы наверняка заметили: в этой статье сообщений об ошибках было больше, чем в любой предыдущей. Это не совпадение — как только программа начинает трогать внешний мир (файлы, сеть, ввод пользователя), всё начинает ломаться по-настоящему. Файла нет, кодировка не та, в столбце с числом оказалось слово.
Пора научиться не бояться красного текста в консоли, а читать его как инструкцию.