Модуль 6 · Урок 6.1
Файлы и внешние данные
Чтение файла целиком и по строкам, заголовок как часть формата, запись, пути, CSV и JSON.
Сумму и наибольшее из 5, −2, 10, 7 мы уже считали: числа вводили с клавиатуры или записывали списком в коде. В задачах ЕГЭ и в жизни чисел тысячи, и они лежат в файле — вводить их руками никто не будет. Программа может сама прочитать файл: открыть его по имени и получить текст целиком или строку за строкой.
Пусть те же числа лежат в файле numbers.txt, по одному на строке:
5-2107Посчитаем для него сумму и наибольшее, а потом разберём файлы с заголовком, запись в файл и готовые форматы CSV и JSON.
Открыть файл
Чтобы прочитать файл, программа открывает его по пути и в кодировке. Путь говорит, где лежит файл. Кодировка — правило, по которому байты на диске превращаются в буквы; для русского текста это "utf-8".
with open("numbers.txt", "r", encoding="utf-8") as file: text = file.read()print(repr(text))open открывает файл, "r" означает чтение. with … as file даёт открытому файлу имя file и закрывает файл, когда блок кончится. read() вернул весь текст одной строкой, а repr, как в уроке о строках, показал в ней переводы строк \n.
Путь "numbers.txt" — относительный: Python ищет файл в рабочей папке, откуда запущена программа. Абсолютный путь начинается с диска и находит файл откуда угодно:
/.Проще всего держать numbers.txt рядом с main.py в папке проекта: в PyCharm — правой кнопкой по папке проекта, New → File; в VS Code — кнопка New File над списком файлов. Тогда при запуске из IDE файл найдётся. При запуске из терминала сначала перейди в эту папку командой cd, как в первом уроке. Если файла нет по указанному пути, программа остановится:
with open("missing.txt", encoding="utf-8") as file: print(file.read())Тонкости:with и ошибки
with закрывает файл и тогда, когда внутри блока возникла ошибка. Без него файл закрывают вызовом file.close(), и его легко забыть.
Целиком или построчно
Небольшой файл читают целиком, большой — построчно. Целиком: read() отдаёт весь текст, split() режет его по пробелам и переводам строк, дальше — знакомые sum и max:
with open("numbers.txt", encoding="utf-8") as file: numbers = [int(part) for part in file.read().split()]print(sum(numbers), max(numbers))Так весь файл сразу оказывается в памяти — для миллиона строк это много. Построчно: цикл for line in file выдаёт строки файла по одной, и сумму с наибольшим можно считать по ходу, как в уроке о while. best = None — «наибольшего пока нет», как в уроке о for:
total = 0best = Nonewith open("numbers.txt", encoding="utf-8") as file: for line in file: number = int(line) total += number if best is None or number > best: best = numberprint(total, best)Каждая строка приходит вместе с переводом строки в конце: '5\n'. int допускает пробелы и перевод строки по краям, поэтому .strip() здесь не нужен. Пройди цикл по шагам:
- Файл открыт. total = 0, best = None — наибольшего пока нет.
- for берёт строку '5' с переводом строки; int(line) = 5; total = 5; best было None — теперь 5.
- for берёт строку '-2' с переводом строки; int(line) = -2; total = 3; -2 не больше 5 — best не меняется.
- for берёт строку '10' с переводом строки; int(line) = 10; total = 13; 10 > 5 — best = 10.
- for берёт строку '7' с переводом строки; int(line) = 7; total = 20; 7 не больше 10 — best не меняется. Строки кончились — print(total, best) печатает 20 10.
Пустая строка в файле вызовет ошибку int — и это полезно, если формат обещал число на каждой строке. Пропускать пустые строки стоит, только если так сказано в условии.
Тонкости:когда strip() вредит
В задачах на обработку текста пробелы по краям строки бывают частью данных, и .strip() их уничтожит. Чтобы снять только перевод строки, пиши line.rstrip("\n").
Заголовок файла
Заголовок файла — часть формата, а не очередное значение. Файл может начинаться количеством записей или настройками, и их нельзя складывать с данными. Пусть в файле header.txt первая строка — количество чисел:
34-16with open("header.txt", encoding="utf-8") as file: expected_count = int(file.readline()) numbers = [int(line) for line in file] if len(numbers) != expected_count: print("Количество записей не совпадает с заголовком")else: print(sum(numbers))У открытого файла есть позиция — место, с которого начнётся следующее чтение. readline() читает одну строку и сдвигает позицию, а цикл по файлу продолжает с того места, где она стоит. Поэтому 3 не попало в сумму: 4 − 1 + 6 = 9. Пройди это чтение по шагам:
- Файл открыт: позиция перед первой строкой.
- readline() забирает первую строку — заголовок 3; позиция теперь перед строкой 4.
- Цикл по файлу продолжает с текущей позиции и забирает оставшиеся строки: 4, −1, 6.
- Позиция в конце файла: новое чтение возвращает пустую строку. Чтобы читать снова, файл открывают заново.
Прочитанное второй раз не читается: после read() или цикла до конца новое чтение вернёт пустую строку. Чтобы читать снова, файл открывают заново.
Если первая строка содержит сразу два числа, сначала разбей её через split, затем преобразуй и распакуй:
header = "100 2500"count, capacity = [int(part) for part in header.split()]print(count, capacity)Формат файла задаёт условие. Иногда заголовка нет вообще. Самая частая ошибка — правильный цикл для неверно понятого формата.
Запись в файл
Режим открытия решает, читать, перезаписать или дописать файл. Его передают вторым аргументом open:
| Режим | Что делает | Если файла нет |
|---|---|---|
"r" | Читает; режим по умолчанию | Ошибка FileNotFoundError |
"w" | Пишет заново: прежнее содержимое сразу стирается | Создаёт |
"a" | Дописывает в конец, прежнее не трогает | Создаёт |
Запишем сумму сквозной задачи в файл result.txt:
total = 20with open("result.txt", "w", encoding="utf-8") as file: file.write("Сумма: " + str(total) + "\n")На экран программа ничего не печатает, а рядом с main.py появляется файл:
Сумма: 20write принимает только строку и сам не добавляет перевод строки — поэтому str и "\n". Другой способ — print("Сумма:", total, file=file): он ставит пробел и перевод строки сам. Не открывай входной файл в режиме "w": он будет очищен ещё до чтения.
Тонкости:режимы x и b
"x" создаёт новый файл и останавливается с ошибкой, если такой уже есть. Буква b добавляется для работы с байтами, а не с текстом: "rb", "wb".
pathlib
pathlib собирает путь из частей. Модуль подключается так же, как math в уроке о модулях: from pathlib import Path.
from pathlib import Path path = Path("data") / "numbers.txt"print(path.resolve())Знак / между Path и строкой соединяет части пути, а не делит. resolve() показывает абсолютный путь — у каждого компьютера он свой, поэтому вывод здесь не показан. Это удобно, когда файл не находится: видно, где Python его искал.
Тонкости:путь рядом со скриптом
Путь к файлу рядом с самим скриптом, откуда бы его ни запустили: Path(__file__).resolve().parent / "numbers.txt". Специальное имя __file__ — путь к файлу модуля; в интерактивной оболочке его нет.
CSV и JSON
Для таблиц и вложенных записей есть готовые форматы CSV и JSON. CSV — таблица в текстовом файле: запись на строке, поля разделены заданным знаком. Поле может содержать в кавычках сам разделитель, поэтому простое split(",") CSV надёжно не разберёт. В стандартной библиотеке есть модуль csv:
name;scoreАнна;91Илья;84Марк;88import csv with open("students.csv", encoding="utf-8", newline="") as file: reader = csv.DictReader(file, delimiter=";") for row in reader: print(row["name"], int(row["score"]))Первая строка файла — заголовок name;score. DictReader превращает каждую следующую строку в словарь: ключи — имена столбцов, значения — строки, поэтому балл переводим через int. newline="" оставляет переводы строк модулю csv.
JSON — текстовая запись словарей, списков, строк и чисел. Модуль json превращает их в текст и обратно:
import json data = {"name": "Анна", "scores": [91, 84]}text = json.dumps(data, ensure_ascii=False)print(text)restored = json.loads(text)print(restored == data)dumps создаёт текст, loads разбирает его; dump и load делают то же с открытым файлом. ensure_ascii=False оставляет кириллицу читаемой, indent=2 разложил бы запись по строкам с отступами. Кортеж JSON запишет как список и вернёт списком, а множество не запишет вовсе.
Нет подходящего элемента
Если подходящих строк нет вовсе, None остаётся до конца. Найдём наибольшее отрицательное число файла или сообщим, что отрицательных нет:
best = Nonewith open("numbers.txt", encoding="utf-8") as file: for line in file: value = int(line) if value < 0 and (best is None or value > best): best = value if best is None: print("Отрицательных чисел нет")else: print(best)Это тот же цикл, что для наибольшего, только с условием value < 0. Короткое вычисление or не даёт сравнить число с None: при best is None правая часть не вычисляется. Для файла без отрицательных чисел или пустого файла программа честно напишет «Отрицательных чисел нет».
Итог
Файл — ещё один источник строк для знакомого цикла: открыл, прочитал по формату, посчитал. Вот что понадобится дальше.
with open(…)with open(путь, encoding="utf-8") as file:открывает файл и закрывает его после блока.- ПутьОтносительный — от рабочей папки, абсолютный — от корня диска; файл держат рядом с
main.py. - Чтение
read()— весь текст,for line in file— строка за строкой,readline()— одна строка; позиция движется только вперёд. - ЗаголовокЗаголовок читают отдельно; остальные строки — данные.
- Запись
"w"стирает файл,"a"дописывает;writeне добавляет перевод строки. - CSV и JSONТаблицы читают модулем
csv, вложенные записи хранят в JSON.
Проверьте себя
Ответь сначала сам, потом открой ответ.
Напиши программу: найди в файле наименьшее положительное число или сообщи, что положительных нет. Что она напечатает для numbers.txt? А для файла из чисел −3, −8?
ОтветСкрыть ответ
5; для −3, −8 — «Положительных чисел нет».
Что вернёт file.readline(), если перед ним в том же блоке with уже выполнили file.read()?
ОтветСкрыть ответ
Пустую строку '': read() уже передвинул позицию в конец файла.
Файл header.txt сложили, забыв о заголовке: sum([int(line) for line in file]). Какая получится сумма и почему она неверна?
ОтветСкрыть ответ
12: в сумму попал заголовок 3, а сумма самих данных 4, −1, 6 — это 9.
Чем отличаются режимы "w" и "a" для существующего файла?
ОтветСкрыть ответ
"w" стирает файл перед записью, "a" дописывает в конец.
Программа с open("numbers.txt") работает в PyCharm, а из терминала падает с FileNotFoundError. Почему?
ОтветСкрыть ответ
Путь относительный, и Python ищет файл в рабочей папке. В терминале программу запустили из другой папки — перейди командой cd в папку проекта.
Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Напиши программу: наименьшее и наибольшее за один проход
Скачай файл numbers.txt — в нём числа урока, по одному на строке. Напиши программу, которая за один проход по файлу находит наименьшее и наибольшее число и печатает их одним print. Запусти её и запиши, что она напечатала.
Запишите два числа через пробел: наименьшее, затем наибольшее.
Подсказка
Две переменные с None в начале, как best в уроке, — обнови обе в одном цикле for line in file.
Разбор
Например:
smallest = Nonelargest = Nonewith open("numbers.txt", encoding="utf-8") as file: for line in file: number = int(line) if smallest is None or number < smallest: smallest = number if largest is None or number > largest: largest = numberprint(smallest, largest)В файле 5, −2, 10, 7: наименьшее −2, наибольшее 10.
Ответ: -2 10
Что напечатает программа?
Скачай файл header.txt из урока: первая строка — количество чисел, дальше сами числа. Что напечатает программа?
with open("header.txt", encoding="utf-8") as file: count = int(file.readline()) numbers = [int(line) for line in file]print(count, sum(numbers))Запишите напечатанные числа через пробел в порядке вывода.
Подсказка
readline() забирает только первую строку, цикл по файлу продолжает со второй.
Разбор
readline() читает заголовок 3; цикл получает остальные строки 4, −1, 6, их сумма 9.
Ответ: 3 9
Что напечатает программа?
with open("log.txt", "w", encoding="utf-8") as file: file.write("A\n")with open("log.txt", "a", encoding="utf-8") as file: file.write("B\n")with open("log.txt", "w", encoding="utf-8") as file: file.write("C\n")with open("log.txt", encoding="utf-8") as file: print(file.read().strip())Введите то, что напечатала программа.
Подсказка
Режим "w" стирает прежнее содержимое файла, "a" дописывает в конец.
Разбор
После первых двух блоков в файле строки A и B, но третий блок открывает файл в режиме "w" и стирает их. Остаётся только C.
Ответ: C
Что напечатает программа?
import json print(json.loads(json.dumps((1, 2))))Запишите список так, как его печатает Python.
Подсказка
В JSON нет кортежей: кортеж записывается как массив и читается обратно списком.
Разбор
dumps превращает кортеж (1, 2) в текст [1, 2], а loads читает этот массив как список.
Ответ: [1, 2]
Напиши программу: лучший ученик из CSV
Скачай файл students.csv из урока. Напиши программу: прочитай файл через csv.DictReader и напечатай одним print имя и балл ученика с наибольшим баллом. Запусти её и запиши, что она напечатала.
Запишите имя и балл через пробел, как их напечатала программа.
Подсказка
Балл из CSV — строка: сравнивай int(row["score"]), а не саму строку.
Разбор
Например:
import csv best_name = Nonebest_score = Nonewith open("students.csv", encoding="utf-8", newline="") as file: for row in csv.DictReader(file, delimiter=";"): score = int(row["score"]) if best_score is None or score > best_score: best_name = row["name"] best_score = scoreprint(best_name, best_score)Баллы 91, 84, 88 — наибольший у Анны.
Ответ: Анна 91