Skip to content

Глава 10. Файлы и CSV: чтение выгрузки и запись отчёта

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


10.1 Чтение текстового файла: with open(..., encoding='utf-8')

Зачем это нужно. До сих пор все данные жили прямо в коде — списки и словари, набранные руками. На практике выгрузка из системы или экспорт из таблицы приходит файлом на диске, и первый шаг любой обработки — открыть этот файл и прочитать его содержимое.

// PYTHON_EXECUTOR (Pyodide Wasm)
Иванов Иван,78
Смирнова Ольга,92
Ким Юрий,50

open() возвращает объект файла, with — конструкция, которая гарантированно закроет файл после выхода из блока, даже если внутри случится ошибка. Явный encoding='utf-8' — не формальность: без него Python на разных системах может подставить свою кодировку по умолчанию (например, cp1251 на Windows), и тогда кириллица, сохранённая в utf-8 (стандарт большинства выгрузок), прочитается неправильно или не прочитается вовсе.

⚠️ Подводный камень

Первая ловушка — опечатка в имени файла или неверный путь.

// PYTHON_EXECUTOR (Pyodide Wasm)
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' при чтении файла с кириллицей.

// PYTHON_EXECUTOR (Pyodide Wasm)
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 подставил кодировку операционной системы. Кириллица в этих кодировках кодируется разными байтами.

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)

Пояснение логики: encoding='utf-8' в open() стоит указывать всегда, как только в файле есть кириллица, — это не зависит от того, на какой машине запускается скрипт. Путь к файлу проверяют так же внимательно, как имя переменной: опечатка в нём не подсвечивается заранее, а падает только в момент выполнения строки с open().


10.2 Разбор выгрузки: csv.reader и csv.DictReader

Зачем это нужно. Построчное чтение работает для простого текста, но настоящая выгрузка — это таблица: заголовок и строки с полями через запятую (CSV, comma-separated values). Резать такую строку руками через .split(',') рискованно — это ломается на текстовых полях, где запятая — часть значения, в кавычках. Модуль csv берёт разбор строк на себя.

// PYTHON_EXECUTOR (Pyodide Wasm)
Иванов Иван: 78
Смирнова Ольга: 92
Ким Юрий: 50

Удобнее обращаться к полям по имени, а не по номеру — для этого есть csv.DictReader: он сам читает первую строку как заголовки и отдаёт каждую следующую строку словарём.

// PYTHON_EXECUTOR (Pyodide Wasm)
Иванов Иван: 78
Смирнова Ольга: 92
Ким Юрий: 50

⚠️ Подводный камень

Все значения, которые отдаёт csv, — строки, даже если выглядят как числа. Арифметика с ними падает.

// PYTHON_EXECUTOR (Pyodide Wasm)
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().

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)
Сумма баллов: 220

Пояснение логики: строки к числам стоит приводить сразу при чтении строки, а не откладывать на потом — тогда в остальном коде score уже гарантированно число, и не приходится помнить в каждом месте, что где-то в переменной может прятаться строка.


10.3 Запись отчёта: режимы 'w' и 'a', csv.writer, newline=''

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

// PYTHON_EXECUTOR (Pyodide Wasm)

'w' (write) открывает файл для записи: если файла не было — создаёт, если был — начинает с чистого листа. 'a' (append) дописывает в конец существующего файла, не трогая то, что там уже есть. newline='' в open() — обязательный спутник csv.writer в этой книге, о нём — ниже.

⚠️ Подводный камень

Первая ловушка — режим 'w' не предупреждает, что в файле что-то было: если случайно открыть в нём отчёт прошлого запуска, старые данные исчезают без единого сообщения об ошибке.

// PYTHON_EXECUTOR (Pyodide Wasm)

Traceback не появится — файл честно перезаписался, как и попросили.

Вторая ловушка — забытый newline='' при открытии файла для csv.writer: в файле появляются пустые строки между записями.

// PYTHON_EXECUTOR (Pyodide Wasm)
student_name,average_score
Иванов Иван,71.7
ОшибкаЧто значит для PythonЧеловеческий перевод
— (тихая ошибка)«'w' — режим “начать с чистого листа”»Открытие существующего файла с накопленными данными в режиме 'w' стирает их безвозвратно и без предупреждения. Для дозаписи нужен режим 'a'.
— (тихая ошибка)«Без newline='' перевод строки добавляется дважды»csv.writer сам ставит \r\n после каждой строки, а текстовый режим файла на некоторых системах ещё раз переводит \n в \r\n — в сумме между записями остаются пустые строки.

Правильный паттерн

// PYTHON_EXECUTOR (Pyodide Wasm)
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

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

// PYTHON_EXECUTOR (Pyodide Wasm)
--- ОТЧЁТ СОХРАНЁН В 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' стирает содержимое файла без предупреждения

Дальше: Ошибки и исключения