Исключение FileNotFoundError и менеджер контекста (with) для файлов

Курс по Python: https://stepik.org/course/100707

На предыдущем занятии я отмечал, что если указать неверный путь к файлу, то возникнет ошибка:

FileNotFoundError

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

Для обработки подобных ошибок (или, как говорят, исключений) существует специальная группа операторов:

try / except / finally

о которых мы подробно будем говорить в части объектно-ориентированного программирования на Python. Но, несмотря на то, что это выходит за рамки базового курса, я решил показать, как использовать эти операторы при работе с файлами. Иначе, ваши программы будут заведомо содержать серьезную уязвимость при обращении к файлам.

Формально, операторы try / except / finally имеют, следующий синтаксис (определение):

try:
        блок операторов
        критического кода
except [исключение]:
        блок операторов
        обработки исключения
finally:
        блок операторов
        всегда исполняемых
        вне зависимости, от
        возникновения исключения

И в нашем случае их можно записать, так:

try:
    file = open("my_file.txt", encoding='utf-8')
    s = file.readlines()
    print(s)
    file.close()
except FileNotFoundError:
    print("Невозможно открыть файл")

Смотрите, здесь функция open() находится внутри блока try, поэтому, если возникнет исключение FileNotFoundError, то выполнение программы перейдет в блок except и отобразится строка «Невозможно открыть файл». Иначе, мы прочитаем все строки из файла, отобразим их в консоли и закроем файл.

Однако, и в такой реализации не все гладко. В момент считывания информации из файла (в методе readlines()) также может возникнуть исключение из-за невозможности считывания информации из файла. Поэтому, совсем надежно можно было бы реализовать эту программу, следующим образом:

try:
    file = open("my_file.txt", encoding='utf-8')
 
    try:
        s = file.readline()
        print(s)
    finally:
        file.close()
        
except FileNotFoundError:
     print("Невозможно открыть файл")
except: 
    print("Ошибка при работе с файлом")

Мы здесь прописываем еще один вложенный блок try, который будет учитывать все возможные исключения и при их возникновении мы обязательно перейдем в блок finally для закрытия файла. Это важная процедура – любой ранее открытый файл (функцией open()) следует закрывать, даже при возникновении исключений. И вот такая конструкция try / finally нам гарантирует его закрытие, что бы ни произошло в момент работы с ним. Но блок try / finally не отлавливает исключения, поэтому они передаются внешнему блоку try и здесь мы должны их обработать. Я сделал это через второй блок except, в котором не указал никакого типа исключений. В результате, он будет реагировать на любые не обработанные ошибки, то есть, в нашем случае – на любые ошибки, кроме FileNotFoundError.

Менеджер контекста для файлов

Более я не буду углубляться в работу блоков try / except / finally. Приведенного материала пока вполне достаточно, а в заключение этого занятия расскажу о замене блока try / finally так называемым файловым менеджером контекста, как это принято делать в программах на Python.

Так вот, в языке Python существует специальный оператор with, который, можно воспринимать как аналог конструкции try / finally и в случае работы с файлами записывается, следующим образом:

try:
    with open("my_file.txt", encoding='utf-8') as file:
        s = file.readlines()
        print( s )
 
except FileNotFoundError:
     print("Невозможно открыть файл")
except:
    print("Ошибка при работе с файлом")

Смотрите, мы здесь сразу вызываем функцию open(), создается объект file, через который, затем, вызываем методы для работы с файлом. Причем, все операторы должны быть записаны внутри менеджера контекста, так как после его завершения файл закрывается автоматически. Именно поэтому здесь нет необходимости делать это вручную.

При работе с менеджером контекста следует иметь в виду, что он не обрабатывает никаких ошибок (исключений) и все, что происходит, передается вышестоящему блоку try, где они и обрабатываются. Но в любом случае: произошли ошибки или нет, файл будет автоматически закрыт. В этом легко убедиться, если добавить блок finally для оператора try, в котором отобразить флаг закрытия файла:

try:
    with open("my_file.txt", encoding='utf-8') as file:
        s = file.readlines()
        print( s )
 
except FileNotFoundError:
     print("Невозможно открыть файл")
except:
    print("Ошибка при работе с файлом")
finally:
    print(file.closed)

После запуска программы видим значение True, то есть, файл был закрыт. Даже если произойдет критическая ошибка, например, вызовем функцию int() для строки s:

int(s)

то, снова видим значение True – файл был закрыт. Вот в этом удобство использования менеджера контекста при работе с файлами.

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

Курс по Python: https://stepik.org/course/100707

Видео по теме