Wird die Datei in Python immer noch geschlossen, wenn ich in einen "with" -Block zurückkehre?

256

Folgendes berücksichtigen:

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

Wird die Datei ordnungsgemäß geschlossen oder wird returnder Kontextmanager irgendwie umgangen ?

Leichte Brise
quelle

Antworten:

238

Ja, es verhält sich wie ein finallyBlock nach dem anderen try, dh es wird immer ausgeführt (es sei denn, der Python-Prozess wird natürlich auf ungewöhnliche Weise beendet).

Es wird auch in einem der Beispiele von PEP-343 erwähnt, das die Spezifikation für die withAussage ist:

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

Erwähnenswert ist jedoch, dass Sie Ausnahmen, die durch den open()Aufruf ausgelöst werden, nicht einfach abfangen können , ohne den gesamten withBlock in einen try..exceptBlock zu setzen, der normalerweise nicht das ist, was man will.

DiebMaster
quelle
8
elsekönnte hinzugefügt werden with, um dieses try with exceptProblem zu lösen . bearbeiten: zur Sprache hinzugefügt
rplnt
7
Ich weiß nicht, ob es relevant ist, aber meines Wissens Process.terminate()ist es eines der wenigen (einzigen?) Szenarien, die den Aufruf einer finallyAnweisung nicht garantieren : "Beachten Sie, dass Exit-Handler und schließlich Klauseln usw.
Rik Poggi
@RikPoggi os._exitwird manchmal verwendet - es beendet den Python-Prozess, ohne Bereinigungshandler aufzurufen.
Acumenus
2
Vielleicht die Schlange ein wenig verspotten, aber was ist, wenn ich einen Generatorausdruck aus dem withBlock zurückgebe, bleibt die Garantie so lange bestehen, wie der Generator weiterhin Werte liefert? solange irgendetwas darauf verweist? Muss ich delder Variablen, die das Generatorobjekt enthält, einen anderen Wert verwenden oder einen anderen Wert zuweisen?
Bestätigen Sie den
1
@davidA Nach dem Schließen der Datei sind die Referenzen weiterhin verfügbar. jedoch alle Versuche , verwenden Sie die Verweise auf Zug- / Druckdaten zu / von der Datei geben: ValueError: I/O operation on closed file..
RWDJ
36

Ja.

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

..ist ziemlich gleichbedeutend mit:

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

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

Genauer gesagt wird die __exit__Methode in einem Kontextmanager beim Verlassen des Blocks immer aufgerufen (unabhängig von Ausnahmen, Rückgaben usw.). Die __exit__Methode des Dateiobjekts ruft nur auf f.close()(z. B. hier in CPython ).

dbr
quelle
30
Ein interessantes Experiment, um die Garantie zu zeigen, die Sie vom finallySchlüssel erhalten, ist : def test(): try: return True; finally: return False.
Ehsan Kia
20

Ja. Im Allgemeinen wird die __exit__Methode eines With Statement Context Managers tatsächlich aufgerufen, wenn ein returnKontext innerhalb des Kontexts angezeigt wird . Dies kann mit folgendem getestet werden:

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

Die Ausgabe ist:

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

Die obige Ausgabe bestätigt, dass __exit__trotz des frühen aufgerufen wurde return. Daher wird der Kontextmanager nicht umgangen.

Scharfsinn
quelle
4

Ja, aber in anderen Fällen kann es zu Nebenwirkungen kommen, da im __exit__Block möglicherweise etwas getan werden sollte (z. B. das Löschen des Puffers)

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'
virusdefender
quelle