Как учиться

Работа с файлами

Сообщение FileNotFoundError ученик читает как «файла нет» и идёт проверять, лежит ли он на месте. Файл лежит там, где ему положено, рядом с программой. Но Python ищет его по относительному пути от текущей папки, и текущей может оказаться любая: та, из которой среда разработки запустила интерпретатор, хотя программа сохранена совсем в другом месте. Тот же ученик меняет имя файла, переименовывает папку, перекладывает данные на рабочий стол и через час приходит к выводу, что Python его не любит.

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

Откуда Python ищет файл

Если в open указано просто имя, Python отсчитывает путь от текущей папки процесса. Узнать, какая она, можно функцией getcwd из модуля os, и первая диагностическая привычка: перед чтением напечатать результат getcwd и список файлов в этой папке. Двадцать секунд, и вопрос «где мы» закрыт.

Выхода два: класть файл в ту папку, откуда запускается программа, или указывать полный путь. Полный путь на Windows несёт собственную ловушку. Если в пути на Windows после диска идёт папка temp, то обратная косая черта перед буквой t читается Python как знак табуляции, а перед буквой n как перевод строки, и путь рассыпается так, что глазами не видно. Спасает приставка r перед кавычкой, которая отключает такую обработку, или прямая косая черта, с которой Python справляется на любой системе.

Чтение: что именно приходит из файла

Самая частая форма записи, на которую стоит сразу опереться: with open('data.txt') as f: и дальше работа внутри блока. Блок with закрывает файл сам, и ни про закрытие, ни про открытый дескриптор думать не приходится. Внутри блока есть несколько способов читать, и они не равны. Метод read отдаёт всё содержимое одной строкой. Метод readline берёт по одной строке за вызов. Цикл for line in f: проходит строки по очереди и не держит весь файл в памяти.

Главное, что надо знать про любую прочитанную строку: на конце у неё стоит символ перевода строки, кроме, возможно, последней. Число '5' из файла приходит как '5' и невидимый перевод. Хорошая новость: int и float такие хвосты прощают, и пятёрка с переводом строки на конце превращается в число пять. Плохая новость: две строки, из которых одна прочитана с переводом, а другая нет, не равны друг другу, и сравнение «5» и «5» вдруг выдаёт ложь. Метод strip убирает пробелы и переводы с обоих концов, и привычка вызывать его на каждой прочитанной строке дешевле расследования.

Вторая ловушка — тип. Прочитанное из файла остаётся строкой, и пока вы не превратили его в число, сложение двух значений даёт склейку: '12' и '7' в сумме дают '127'. А сравнение строк идёт по символам: '10' оказывается меньше '9', и максимум из списка таких «чисел» выдаёт девятку. Программа не падает и ответ выглядит как ответ, поэтому такие ошибки находятся только проверкой на маленьком примере, посчитанном руками.

Есть и короткий приём для файла, где числа просто идут одно за другим: read и сразу split без аргумента. Метод делит текст по любым пробелам и переводам строки и возвращает список слов за один вызов. Цена у такого удобства одна: границы между строками исчезают, и если по смыслу задачи строка файла означала отдельную запись, то после split записи слились в общий список. Выбирайте способ по тому, значима ли для задачи граница строки.

Первая строка файла часто не относится к данным: в ней может стоять число значений, названия столбцов, а то и пустота. Ученик, который не открыл файл глазами и не напечатал первые строки, считает заголовок за данные и получает ValueError на первой же строке.

Формат и кодировка

Несколько особенностей формата регулярно ломают чтение, и почти все они из мира электронных таблиц. Десятичная запятая: float('3,5') выдаёт ошибку, потому что Python знает только точку, и перед превращением в число запятую заменяют методом replace. Разделитель столбцов: файл CSV, сохранённый из электронной таблицы в русской локали, обычно разделён точкой с запятой, а не запятой, ведь запятая занята десятичным разделителем, и если вызвать split без аргумента, то строка останется целой. Кодировка: русский текст, сохранённый в одной системе и прочитанный в другой, превращается в набор странных символов или в ошибку UnicodeDecodeError, и тогда кодировку указывают явно, в open через encoding.

Для примера разбор одной строки из такого файла: 12;3,5;слово. Метод split с точкой с запятой в аргументе даст три значения. Первое превращается в число прямо. Во втором запятую меняют на точку, и только потом делают из него число. Третье остаётся строкой. Если в строке есть пробелы по краям, strip пригодится до всего остального.

Такие случаи объединяет одно: файл читается без ошибок, а данные оказываются не теми. Приёмом остаётся одно и то же: напечатать вместо превращения в число сам вид строки, через функцию repr. Она показывает и скрытые переводы строки, и пробелы, и необычные символы.

Что означает сообщение

Похожие по смыслу ошибки на деле имеют разные причины, и различать их надо по тексту сообщения. Расшифровка самых частых:

  • FileNotFoundError: Python не нашёл файл по этому пути, и смотреть надо в текущую папку и в написание имени, включая расширение, которое Windows часто скрывает;
  • ValueError при превращении в число: в строке лежит что-то кроме числа: пустая строка, заголовок, слово или запятая вместо точки; перевод строки на конце тут ни при чём;
  • IndexError при обращении к элементу списка: чаще всего в конце файла стоит пустая строка, метод split вернул пустой список, и первого элемента в нём нет;
  • UnicodeDecodeError: файл сохранён в одной кодировке, а читается в другой;
  • PermissionError при записи: файл открыт в другой программе, например в электронной таблице, и система не даёт его перезаписать.

Три из пяти ошибок (путь, кодировка, права) приходят из окружения, и логика программы тут ни при чём. Ученик, который при любой ошибке сразу переписывает алгоритм, тратит время впустую.

Запись и размер

Запись открывается вторым аргументом open: буква w создаёт файл и стирает прежнее содержимое, буква a дописывает в конец. Открыв с w файл, в котором лежали нужные данные, их теряют сразу. Если файл не закрыт и блока with нет, записанное может остаться в буфере и до диска не дойти, и тогда на экране программа отработала, а файл пуст.

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

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

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

Читать по строкам удобнее и безопаснее, чем весь файл разом, но способ чтения влияет мало, зато многое зависит от того, что вы делаете с данными потом: словари и множества, которым посвящена статья про словари и множества, превращают поиск по всему списку в мгновенный. Общую раскладку заданий первой части экзамена показывает карта заданий, а саму структуру экзамена с языками программирования описывает статья структура ЕГЭ по информатике.

Как искать ошибку на маленьком файле

Начните с файла из пяти строк, который вы сами написали в блокноте, и ответа, посчитанного руками. Пять строк нужны для того, чтобы весь файл помещался на экране и каждую строку можно было сопоставить с тем, что напечатала программа. Сначала программа только читает и печатает каждую строку через repr. Потом превращает в числа и печатает их. Потом считает то, что нужно, и ответ сверяется с ручным. На каждом шаге видно, где потерялось, а большой настоящий файл подключается последним. Если на настоящем файле ответ расходится с ожидаемым, а на маленьком всё сходилось, смотрите на то, чем реальный файл отличается: пустые строки в конце, заголовок, лишние пробелы, другая кодировка. Обычно различие одно, и искать его стоит именно в этих местах.

Репетитор в этой теме полезен именно как второй взгляд: он открывает ваш файл и сразу замечает то, что вы не видите, потому что видели его сотню раз. К этому и стоит готовить занятие: присылать не только программу, но и первые строки файла.

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

Экран репетитор не увидит: ни показа экрана, ни общей доски в видеокомнате нет, поэтому показывать ему надо текст. Код вставляйте в чат (он открывается, когда репетитор получил ваш контакт, а в одно сообщение помещается до 4000 знаков). Если проблема в файле, приложите его: вложений можно сделать до пяти, и каждое может весить до 20 МБ. Занятие не записывается, так что найденную причину ошибки запишите сразу после разбора.

Начать поиск:

Вопросы и ответы

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

Нужно ли закрывать файл? Да. Проще всего блок with.

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

Можно ли читать файл в Паскале или С++ так же? Идея общая: открыть, читать по значению или по строке, закрыть. Названия и тонкости записи у каждого языка свои, а перечисленные ловушки с путём, форматом и типом остаются теми же.

Почему программа работает на моём компьютере и не работает на другом? Вероятнее всего, дело в текущей папке или в кодировке.

Оставить заявку

Ещё в рубрике «Как учиться»