В Python, если я вернусь внутри блока "с", будет ли файл закрыт?

Рассмотрим следующее:

with open(path, mode) as f:
    return [line for line in f if condition]

Будет ли файл закрыт должным образом или с помощью return каким-то образом обходит контекстный менеджер ?

Ответ 1

Да, он действует как блок finally после блока try, т.е. он всегда выполняется (если процесс python не заканчивается необычным образом).

Это также упоминается в одном из примеров PEP-343, который является спецификацией для оператора with:

with locked(myLock):
    # Code here executes with myLock held.  The lock is
    # guaranteed to be released when the block is left (even
    # if via return or by an uncaught exception).

Однако стоит упомянуть то, что вы не можете легко перехватывать исключения, вызванные вызовом open(), не помещая весь блок with внутри блока try..except, который обычно не тот, который вам нужен.

Ответ 2

Да.

def example(path, mode):
    with open(path, mode) as f:
        return [line for line in f if condition]

.. в значительной степени эквивалентен:

def example(path, mode):
    f = open(path, mode)

    try:
        return [line for line in f if condition]
    finally:
        f.close()

Точнее, метод __exit__ в менеджере контекста всегда вызывается при выходе из блока (независимо от исключений, возвратов и т.д.). Метод файла __exit__ просто вызывает f.close() (например, здесь, в CPython)

Ответ 3

В более общем плане, метод __exit__ с оператором контекста Statement действительно будет вызываться в случае return от внутри контекста. Это можно протестировать следующим образом:

class Resource(object):
    def __enter__(self):
        print('Entering context.')
        return self

    def __exit__(self, *exc):
        print('Exiting context.')

def fun():
    with Resource():
        print('Returning inside with-statement.')
        return
    print('Returning outside with-statement.')

fun()

Вывод:

Entering context.
Returning inside with-statement.
Exiting context.

Вышеприведенный вывод подтверждает, что __exit__ был вызван, несмотря на ранний return. Таким образом, менеджер контекста не обходит.