Глава 10. Файлы и CSV: чтение выгрузки и запись отчёта
Задача главы: прочитать реальную выгрузку из журнала как файл на диске и сохранить готовый отчёт отдельным файлом, не потеряв кириллицу и не перезаписав данные по ошибке.
10.1 Чтение текстового файла: with open(..., encoding='utf-8')
Зачем это нужно. До сих пор все данные жили прямо в коде — списки и словари, набранные руками. На практике выгрузка из системы или экспорт из таблицы приходит файлом на диске, и первый шаг любой обработки — открыть этот файл и прочитать его содержимое.
Иванов Иван,78Смирнова Ольга,92Ким Юрий,50open() возвращает объект файла, with — конструкция, которая гарантированно закроет файл после выхода из блока, даже если внутри случится ошибка. Явный encoding='utf-8' — не формальность: без него Python на разных системах может подставить свою кодировку по умолчанию (например, cp1251 на Windows), и тогда кириллица, сохранённая в utf-8 (стандарт большинства выгрузок), прочитается неправильно или не прочитается вовсе.
⚠️ Подводный камень
Первая ловушка — опечатка в имени файла или неверный путь.
Traceback (most recent call last): File "report.py", line 1, in <module> with open('journal.tx', encoding='utf-8') as file:FileNotFoundError: [Errno 2] No such file or directory: 'journal.tx'Вторая ловушка — забытый encoding='utf-8' при чтении файла с кириллицей.
Traceback (most recent call last): File "report.py", line 2, in <module> print(file.read())UnicodeDecodeError: 'charmap' codec can't decode byte 0xd0 in position 0: character maps to <undefined>| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
FileNotFoundError | «По этому пути файла нет» | Опечатка в имени файла или неверный путь — скрипт и файл-выгрузка должны лежать в одной папке, либо путь нужно указать полностью. |
UnicodeDecodeError | «Байты файла не раскладываются в символы по выбранной кодировке» | Файл сохранён в utf-8 (как большинство выгрузок), а Python без явного encoding подставил кодировку операционной системы. Кириллица в этих кодировках кодируется разными байтами. |
Правильный паттерн
Пояснение логики: encoding='utf-8' в open() стоит указывать всегда, как только в файле есть кириллица, — это не зависит от того, на какой машине запускается скрипт. Путь к файлу проверяют так же внимательно, как имя переменной: опечатка в нём не подсвечивается заранее, а падает только в момент выполнения строки с open().
10.2 Разбор выгрузки: csv.reader и csv.DictReader
Зачем это нужно. Построчное чтение работает для простого текста, но настоящая выгрузка — это таблица: заголовок и строки с полями через запятую (CSV, comma-separated values). Резать такую строку руками через .split(',') рискованно — это ломается на текстовых полях, где запятая — часть значения, в кавычках. Модуль csv берёт разбор строк на себя.
Иванов Иван: 78Смирнова Ольга: 92Ким Юрий: 50Удобнее обращаться к полям по имени, а не по номеру — для этого есть csv.DictReader: он сам читает первую строку как заголовки и отдаёт каждую следующую строку словарём.
Иванов Иван: 78Смирнова Ольга: 92Ким Юрий: 50⚠️ Подводный камень
Все значения, которые отдаёт csv, — строки, даже если выглядят как числа. Арифметика с ними падает.
Traceback (most recent call last): File "report.py", line 6, in <module> total_score += row['score']TypeError: unsupported operand type(s) for +=: 'int' and 'str'| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
TypeError | «Нельзя сложить int и str» | Модуль csv всегда отдаёт значения строками — даже колонку с баллами. Это та же ловушка, что с input() в главе Переменные и ввод данных, только источник строк теперь файл, а не клавиатура. Перед арифметикой нужен int() или float(). |
Правильный паттерн
Сумма баллов: 220Пояснение логики: строки к числам стоит приводить сразу при чтении строки, а не откладывать на потом — тогда в остальном коде score уже гарантированно число, и не приходится помнить в каждом месте, что где-то в переменной может прятаться строка.
10.3 Запись отчёта: режимы 'w' и 'a', csv.writer, newline=''
Зачем это нужно. Результат расчётов нужно не только напечатать на экране, но и сохранить файлом — таким, который можно переслать коллеге или открыть в Excel. За это отвечает csv.writer: он сам расставляет запятые и кавычки там, где нужно.
'w' (write) открывает файл для записи: если файла не было — создаёт, если был — начинает с чистого листа. 'a' (append) дописывает в конец существующего файла, не трогая то, что там уже есть. newline='' в open() — обязательный спутник csv.writer в этой книге, о нём — ниже.
⚠️ Подводный камень
Первая ловушка — режим 'w' не предупреждает, что в файле что-то было: если случайно открыть в нём отчёт прошлого запуска, старые данные исчезают без единого сообщения об ошибке.
Traceback не появится — файл честно перезаписался, как и попросили.
Вторая ловушка — забытый newline='' при открытии файла для csv.writer: в файле появляются пустые строки между записями.
student_name,average_score
Иванов Иван,71.7| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
| — (тихая ошибка) | «'w' — режим “начать с чистого листа”» | Открытие существующего файла с накопленными данными в режиме 'w' стирает их безвозвратно и без предупреждения. Для дозаписи нужен режим 'a'. |
| — (тихая ошибка) | «Без newline='' перевод строки добавляется дважды» | csv.writer сам ставит \r\n после каждой строки, а текстовый режим файла на некоторых системах ещё раз переводит \n в \r\n — в сумме между записями остаются пустые строки. |
Правильный паттерн
student_name,average_scoreИванов Иван,71.7Пояснение логики: newline='' в open() отдаёт управление переводом строк целиком модулю csv — он сам знает нужный формат. Выбор между 'w' и 'a' — вопрос смысла: новый отчёт на каждый запуск — 'w'; журнал, который должен копить историю всех запусков, — 'a' (и тогда заголовок пишут только один раз, при первом создании файла).
10.4 Мини-словарик ошибок десятой главы
| Ошибка | Что значит для Python | Человеческий перевод |
|---|---|---|
FileNotFoundError | «По этому пути файла нет» | Опечатка в имени файла, неверный путь или файл лежит в другой папке относительно скрипта. |
UnicodeDecodeError | «Байты не раскладываются в символы по этой кодировке» | Файл сохранён в utf-8, а Python читал его без явного encoding='utf-8' и подставил кодировку операционной системы. |
TypeError (арифметика со строкой) | «Нельзя сложить int и str» | Значения из csv — всегда строки, даже колонка с баллами. Перед арифметикой нужен int() или float(). |
| — (тихая ошибка) | «'w' — режим “начать с чистого листа”» | Открытие файла с накопленными данными в режиме 'w' стирает их без предупреждения; для дозаписи нужен режим 'a'. |
| — (тихая ошибка) | «Без newline='' перевод строки добавляется дважды» | Между записями csv.writer появляются пустые строки — файл всё ещё читается как CSV, но выглядит неаккуратно. |
Мини-кейс: выгрузка journal.csv → расчёт средних → файл report.csv
Задача: прочитать выгрузку с результатами по предметам, сгруппировать баллы по ученику, посчитать средний балл и статус для каждого и сохранить получившийся отчёт отдельным файлом.
--- ОТЧЁТ СОХРАНЁН В report.csv ---Иванов Иван: средний балл 71.7 — зачётСмирнова Ольга: средний балл 91.7 — зачётКим Юрий: средний балл 48.3 — незачётЧто получилось: report.csv теперь лежит на диске рядом со скриптом и открывается в любой таблице — три колонки, без единой пустой строки между записями. setdefault() из главы Словари собрал плоскую выгрузку (одна строка — один предмет) в словарь списков, а calculate_average() и get_status() из главы Функции сработали без единой правки — ровно то, для чего их выносили в отдельные функции.
⚠️ Если в journal.csv окажется испорченная строка — пустое значение в колонке score или ученик без единой сдачи, — int() или calculate_average() уронят весь скрипт исключением, и отчёт не сохранится вовсе, даже для учеников с нормальными данными. Как перехватывать такие строки и не терять из-за них весь отчёт — тема главы Ошибки и исключения.
Чек-лист главы
- Открываю файлы через
with open(..., encoding='utf-8')— кодировку указываю явно - Знаю, что
FileNotFoundError— это неверный путь или опечатка в имени файла - Знаю, что кириллица без
encoding='utf-8'может привести кUnicodeDecodeError - Читаю CSV через
csv.readerилиcsv.DictReader, а не через ручной.split(',') - Пропускаю строку заголовков через
next(reader)при работе сcsv.reader - Обращаюсь к полям
csv.DictReaderпо имени колонки, а не по номеру - Помню, что значения из
csv— всегда строки, и привожу их к числу черезint()до арифметики - Выбираю
'w'для нового отчёта на каждый запуск и'a'для журнала, который копит историю - Указываю
newline=''при открытии файла дляcsv.writer, чтобы не получить пустые строки - Понимаю, что режим
'w'стирает содержимое файла без предупреждения
Дальше: Ошибки и исключения