К содержанию

Модуль 6 · Урок 6.1

Файлы и внешние данные

Чтение файла целиком и по строкам, заголовок как часть формата, запись, пути, CSV и JSON.

5 задач

Сумму и наибольшее из 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))
'5\n-2\n10\n7\n'

open открывает файл, "r" означает чтение. with … as file даёт открытому файлу имя file и закрывает файл, когда блок кончится. read() вернул весь текст одной строкой, а repr, как в уроке о строках, показал в ней переводы строк \n.

Путь "numbers.txt" — относительный: Python ищет файл в рабочей папке, откуда запущена программа. Абсолютный путь начинается с диска и находит файл откуда угодно:

Два пути ведут к одному файлу: относительный отсчитывается от рабочей папки, абсолютный — от корня диска. В пути 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())
FileNotFoundError: [Errno 2] No such file or directory: 'missing.txt'
Тонкости: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))
20 10

Так весь файл сразу оказывается в памяти — для миллиона строк это много. Построчно: цикл 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)
20 10

Каждая строка приходит вместе с переводом строки в конце: '5\n'. int допускает пробелы и перевод строки по краям, поэтому .strip() здесь не нужен. Пройди цикл по шагам:

  1. Файл открыт. total = 0, best = None — наибольшего пока нет.
  2. for берёт строку '5' с переводом строки; int(line) = 5; total = 5; best было None — теперь 5.
  3. for берёт строку '-2' с переводом строки; int(line) = -2; total = 3; -2 не больше 5 — best не меняется.
  4. for берёт строку '10' с переводом строки; int(line) = 10; total = 13; 10 > 5 — best = 10.
  5. for берёт строку '7' с переводом строки; int(line) = 7; total = 20; 7 не больше 10 — best не меняется. Строки кончились — print(total, best) печатает 20 10.

Пустая строка в файле вызовет ошибку int — и это полезно, если формат обещал число на каждой строке. Пропускать пустые строки стоит, только если так сказано в условии.

Тонкости:когда strip() вредит

В задачах на обработку текста пробелы по краям строки бывают частью данных, и .strip() их уничтожит. Чтобы снять только перевод строки, пиши line.rstrip("\n").

Заголовок файла

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

34-16
with 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))
9

У открытого файла есть позиция — место, с которого начнётся следующее чтение. readline() читает одну строку и сдвигает позицию, а цикл по файлу продолжает с того места, где она стоит. Поэтому 3 не попало в сумму: 4 − 1 + 6 = 9. Пройди это чтение по шагам:

  1. Файл открыт: позиция перед первой строкой.
  2. readline() забирает первую строку — заголовок 3; позиция теперь перед строкой 4.
  3. Цикл по файлу продолжает с текущей позиции и забирает оставшиеся строки: 4, −1, 6.
  4. Позиция в конце файла: новое чтение возвращает пустую строку. Чтобы читать снова, файл открывают заново.

Прочитанное второй раз не читается: после read() или цикла до конца новое чтение вернёт пустую строку. Чтобы читать снова, файл открывают заново.

Если первая строка содержит сразу два числа, сначала разбей её через split, затем преобразуй и распакуй:

header = "100 2500"count, capacity = [int(part) for part in header.split()]print(count, capacity)
100 2500

Формат файла задаёт условие. Иногда заголовка нет вообще. Самая частая ошибка — правильный цикл для неверно понятого формата.

Запись в файл

Режим открытия решает, читать, перезаписать или дописать файл. Его передают вторым аргументом 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 появляется файл:

Сумма: 20

write принимает только строку и сам не добавляет перевод строки — поэтому 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Марк;88
import 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"]))
Анна 91 Илья 84 Марк 88

Первая строка файла — заголовок 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)
{"name": "Анна", "scores": [91, 84]} True

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)
-2

Это тот же цикл, что для наибольшего, только с условием 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.

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1

Напиши программу: найди в файле наименьшее положительное число или сообщи, что положительных нет. Что она напечатает для numbers.txt? А для файла из чисел −3, −8?

ОтветСкрыть ответ
Ответ

5; для −3, −8 — «Положительных чисел нет».

Вопрос 2

Что вернёт file.readline(), если перед ним в том же блоке with уже выполнили file.read()?

ОтветСкрыть ответ
Ответ

Пустую строку '': read() уже передвинул позицию в конец файла.

Вопрос 3

Файл header.txt сложили, забыв о заголовке: sum([int(line) for line in file]). Какая получится сумма и почему она неверна?

ОтветСкрыть ответ
Ответ

12: в сумму попал заголовок 3, а сумма самих данных 4, −1, 6 — это 9.

Вопрос 4

Чем отличаются режимы "w" и "a" для существующего файла?

ОтветСкрыть ответ
Ответ

"w" стирает файл перед записью, "a" дописывает в конец.

Вопрос 5

Программа с open("numbers.txt") работает в PyCharm, а из терминала падает с FileNotFoundError. Почему?

ОтветСкрыть ответ
Ответ

Путь относительный, и Python ищет файл в рабочей папке. В терминале программу запустили из другой папки — перейди командой cd в папку проекта.

Практика

Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.

Напиши программу: наименьшее и наибольшее за один проход

Скачай файл numbers.txt — в нём числа урока, по одному на строке. Напиши программу, которая за один проход по файлу находит наименьшее и наибольшее число и печатает их одним print. Запусти её и запиши, что она напечатала.

numbers.txtЧисла 5, −2, 10, 7 по одному на строке — файл из урокаТекстовый файл · 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))
header.txtФайл из урока: заголовок 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())

Введите то, что напечатала программа.

Что напечатает программа?

import json print(json.loads(json.dumps((1, 2))))

Запишите список так, как его печатает Python.

Напиши программу: лучший ученик из CSV

Скачай файл students.csv из урока. Напиши программу: прочитай файл через csv.DictReader и напечатай одним print имя и балл ученика с наибольшим баллом. Запусти её и запиши, что она напечатала.

students.csvТаблица учеников из урока: имя и балл через точку с запятойТаблица CSV · 47 Б

Запишите имя и балл через пробел, как их напечатала программа.