Czy w Pythonie, jeśli wrócę do bloku „z”, plik nadal będzie zamknięty?

256

Rozważ następujące:

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

Czy plik zostanie poprawnie zamknięty, czy też użycie go returnpomija menedżera kontekstu ?

Lekka bryza
źródło

Odpowiedzi:

238

Tak, działa jak finallyblok po trybloku, tzn. Zawsze wykonuje się (chyba że proces python zakończy się w nietypowy sposób).

Jest również wspomniany w jednym z przykładów PEP-343, który jest specyfikacją withinstrukcji:

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).

Warto jednak wspomnieć, że nie można łatwo wychwycić wyjątków zgłaszanych przez open()połączenie bez umieszczenia całego withbloku w try..exceptbloku, który zwykle nie jest tym, czego chce.

ThiefMaster
źródło
8
elsemożna dodać, withaby rozwiązać ten try with exceptproblem. edycja: dodano do języka
rplnt
7
Nie wiem, czy jest to istotne, ale według mojej wiedzy Process.terminate()jest to jeden z niewielu (jedynych?) Scenariuszy, który nie gwarantuje wywołania finallyinstrukcji: „Pamiętaj, że procedury obsługi wyjścia i klauzule wreszcie itd. Nie będą wykonany."
Rik Poggi
@RikPoggi os._exitjest czasem używany - wychodzi z procesu Python bez wywoływania procedur czyszczących.
Acumenus,
2
Być może trochę drwi z węża, ale co jeśli zwrócę wyrażenie generatora z withbloku, czy gwarancja obowiązuje tak długo, jak długo generator generuje wartości? tak długo, jak cokolwiek to odwołuje? Czy muszę użyć dellub przypisać inną wartość zmiennej, która przechowuje obiekt generatora?
ack
1
@davidA Po zamknięciu pliku odwołania są nadal dostępne; Jednak wszelkie próby używać odniesień do danych pociągnięcie / PUSH do / z pliku da: ValueError: I/O operation on closed file..
RWDJ
36

Tak.

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

.. jest prawie równoważne z:

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

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

Dokładniej, __exit__metoda w menedżerze kontekstu jest zawsze wywoływana przy wychodzeniu z bloku (niezależnie od wyjątków, zwrotów itp.). Metoda obiektu pliku __exit__wywołuje tylko f.close()(np. Tutaj w CPython )

dbr
źródło
30
Ciekawym eksperymentem pokazać gwarancji otrzymasz od finallykeywrod jest: def test(): try: return True; finally: return False.
Ehsan Kia,
20

Tak. Mówiąc bardziej ogólnie, __exit__metoda With Context Context Manager rzeczywiście zostanie wywołana w przypadku returnz wewnątrz kontekstu. Można to przetestować za pomocą:

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

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

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

fun()

Dane wyjściowe to:

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

Powyższe dane wyjściowe potwierdzają, że __exit__wywołano je mimo wczesnego okresu return. W związku z tym menedżer kontekstu nie jest pomijany.

Acumenus
źródło
4

Tak, ale w innych przypadkach może wystąpić pewien efekt uboczny, ponieważ może on zrobić coś (np. Bufor opróżniania) w __exit__bloku

import gzip
import io

def test(data):
    out = io.BytesIO()
    with gzip.GzipFile(fileobj=out, mode="wb") as f:
        f.write(data)
        return out.getvalue()

def test1(data):
    out = io.BytesIO()
    with gzip.GzipFile(fileobj=out, mode="wb") as f:
        f.write(data)
    return out.getvalue()

print(test(b"test"), test1(b"test"))

# b'\x1f\x8b\x08\x00\x95\x1b\xb3[\x02\xff' b'\x1f\x8b\x08\x00\x95\x1b\xb3[\x02\xff+I-.\x01\x00\x0c~\x7f\xd8\x04\x00\x00\x00'
wirusdefender
źródło