Работа с файлами
Сообщение 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.
Почему число из файла нельзя просто сложить с другим числом? Потому что из файла приходит текст. Пока значение не превращено в число, плюс склеивает символы, и вместо суммы получается строка. Превращать надо сразу при чтении, а не позже, когда ошибка уже ушла в середину программы.
Можно ли читать файл в Паскале или С++ так же? Идея общая: открыть, читать по значению или по строке, закрыть. Названия и тонкости записи у каждого языка свои, а перечисленные ловушки с путём, форматом и типом остаются теми же.
Почему программа работает на моём компьютере и не работает на другом? Вероятнее всего, дело в текущей папке или в кодировке.
Оставить заявку
- Татьяна Попова — Информатика и олимпиадное программирование · от 3 000 ₽
- Артём Игоревич Соловьёв — Информатика и математика: ОГЭ и ЕГЭ от практикующего разработчика · от 2 100 ₽
- Илья Германович Воронцов — Информатика: ЕГЭ, олимпиады и алгоритмы для студентов · от 1 700 ₽