Как "с" лучше, чем try/catch, чтобы открыть файл в Python?

Я понял, что инструкция with поможет вам сделать это:

try:
    f = open(my_file)
    do_stuff_that_fails()
except:
    pass
finally:
    f.close()

В:

with open(my_file) as f:
    do_stuff_that_fails()

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

try:
    with open(my_file) as f:
        do_stuff_that_fails()
except (IOError, OSError, Failure) as e:
    do_stuff_when_it_doesnt_work()

Что эквивалентно:

try:
    f = open(my_file)
    do_stuff_that_fails()
except (IOError, OSError, Faillure) as e:
    do_stuff_when_it_doesnt_work()
finally:
    f.close()

Да, вы получили две строки, но вы добавили уровень вложенности, который не упрощает чтение. Является ли цель оператора with сохранением двух строк или я что-то упускаю?

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

Ответ 1

Для начала, это поможет предотвратить проблему, которую вы представили в своем примере try ... finally ....

Как вы его структурировали, если при попытке открыть файл вызывается исключение, вы никогда не свяжете открытый файл с именем f, что приведет либо к NameError в предложении finally (если f никогда не было связано в пределах области действия) или что-то совершенно неожиданное (если оно есть).

Правильная структура (эквивалентная with):

f = open(my_file)

try:
    do_stuff_that_fails()
finally:
    f.close()

(примечание - нет необходимости в предложении except, если вам там нечего делать).

Второй пример аналогичным образом неверен и должен быть структурирован следующим образом:

try:
    f = open(my_file)

    try:
        do_stuff_that_fails()
    except EXPECTED_EXCEPTION_TYPES as e:
        do_stuff_when_it_doesnt_work()
    finally:
        f.close()

except (IOError, OSError) as e:
    do_other_stuff_when_it_we_have_file_IO_problems()

Вторая (как указано в другом ответе), что вы не можете забыть позвонить f.close().

BTW, термин "управление контекстом", а не "управление ресурсами" - оператор with управляет контекстами, некоторые из которых могут быть ресурсами, а другие нет. Например, он также используется с decimal для создания десятичного контекста для конкретного блока кода.

Наконец (отвечая на ваш комментарий к предыдущему ответу), вы никогда не должны полагаться на семантику refcount для обработки ресурсов в Python. Jython, IronPython и PyPy имеют не-refcount семантику, и ничего не мешает CPython идти другим путем (хотя это маловероятно для ближайшего будущего). В жесткой петле (например, os.walk) очень просто выбежать из дескрипторов файлов, если код, основанный на семантике refcount, запускается на виртуальной машине с различным поведением.

Ответ 2

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

try:
    file = open(...)
except OpenErrors...:
    # handle open exceptions
else:
    try:
        # do stuff with file
    finally:
        file.close()

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

with open(...) as file:
    # do stuff with file
except OpenErrors...:
    # handle open exceptions

Но это было сбито.

Наконец, стоит отметить, что вы можете напрямую входить и выходить из контекстных менеджеров:

file = open(...).__enter__()
file.__exit__(typ, val, tb)

Это описано более подробно здесь и здесь.

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

Ответ 3

Это для управления ресурсами... не для того, как вы реагируете на исключение иначе:)

Невозможно "забыть" f.close() при использовании with. Таким образом, он выполняет ту же роль, что и using в С#.

Счастливое кодирование.