In Python, se torno all'interno di un blocco "con", il file si chiuderà ancora?


256

Considera quanto segue:

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

Il file verrà chiuso correttamente o l'utilizzo in returnqualche modo ignora il gestore del contesto ?

Risposte:


238

Sì, si comporta come il finallyblocco dopo un tryblocco, ovvero viene sempre eseguito (a meno che il processo python non termini in modo insolito, ovviamente).

È anche menzionato in uno degli esempi di PEP-343 che è la specifica per l' withaffermazione:

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

Qualcosa che vale la pena ricordare è che non è possibile catturare facilmente le eccezioni generate dalla open()chiamata senza inserire l'intero withblocco all'interno di un try..exceptblocco che di solito non è quello che si desidera.


8
elsepotrebbe essere aggiunto withper risolvere quel try with exceptproblema. modifica: aggiunto alla lingua
rplnt il

7
Non so se sia pertinente, ma per quanto ne so Process.terminate()è uno dei pochi (l'unico?) Scenario che non garantisce la chiamata di una finallydichiarazione: "Nota che i gestori di uscita e infine le clausole, ecc., Non lo saranno eseguito."
Rik Poggi,

A os._exitvolte viene utilizzato @RikPoggi : esce dal processo Python senza chiamare i gestori di cleanup.
Acumenus,

2
Forse schernendo un po 'il serpente, ma cosa succede se restituisco un'espressione di generatore all'interno del withblocco, la garanzia rimane valida finché il generatore continua a produrre valori? fino a quando qualcosa lo fa riferimento? Cioè deldevo usare o assegnare un valore diverso alla variabile che contiene l'oggetto generatore?
ack

1
@davidA Dopo la chiusura del file, i riferimenti sono ancora accessibili; tuttavia, qualsiasi tentativo di utilizzare i riferimenti ai dati di pull / push per / dal file darà: ValueError: I/O operation on closed file..
RWDJ,

36

Sì.

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

..è praticamente equivalente a:

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

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

Più precisamente, il __exit__metodo in un gestore di contesto viene sempre chiamato all'uscita dal blocco (indipendentemente da eccezioni, ritorni ecc.). Il __exit__metodo dell'oggetto file chiama solo f.close()(ad es. Qui in CPython )


30
Un interessante esperimento per mostrare la garanzia che si ottiene dalla finallykeywrod è: def test(): try: return True; finally: return False.
Ehsan Kia,

20

Sì. Più in generale, il __exit__metodo di un Gestore di contesto con dichiarazione verrà effettivamente chiamato in caso di returnall'interno del contesto. Questo può essere testato con quanto segue:

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

L'output è:

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

L'output sopra conferma che è __exit__stato chiamato nonostante i primi return. Pertanto, il gestore del contesto non viene ignorato.


4

Sì, ma potrebbe esserci qualche effetto collaterale in altri casi, perché potrebbe fare qualcosa (come il flushing buffer) in __exit__blocco

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'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.