En Python, si je reviens dans un bloc «avec», le fichier se fermera-t-il toujours?

256

Considérer ce qui suit:

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

Le fichier sera-t-il fermé correctement, ou l'utilisation d'une returnmanière ou d'une autre contourne-t-elle le gestionnaire de contexte ?

Lightbreeze
la source

Réponses:

238

Oui, il agit comme le finallybloc après un trybloc, c'est-à-dire qu'il s'exécute toujours (sauf si le processus python se termine de manière inhabituelle bien sûr).

Il est également mentionné dans l'un des exemples de PEP-343 qui est la spécification de la withdéclaration:

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

Cependant, il convient de mentionner que vous ne pouvez pas facilement détecter les exceptions levées par l' open()appel sans placer le withbloc entier dans un try..exceptbloc qui n'est généralement pas ce que vous voulez .

ThiefMaster
la source
8
elsepourrait être ajouté withpour résoudre ce try with exceptproblème. edit: ajouté à la langue
rplnt
7
Je ne sais pas si c'est pertinent, mais à ma connaissance Process.terminate()est l'un des rares (le seul?) Scénario qui ne garantit pas l'appel d'une finallyinstruction: "Notez que les gestionnaires de sortie et enfin les clauses, etc., ne seront pas réalisé."
Rik Poggi
@RikPoggi os._exitest parfois utilisé - il quitte le processus Python sans appeler de gestionnaires de nettoyage.
Acumenus
2
Peut-être raillant un peu le serpent, mais que se passe-t-il si je renvoie une expression de générateur à l'intérieur du withbloc, la garantie est-elle valable aussi longtemps que le générateur continue de produire des valeurs? aussi longtemps que quelque chose y fait référence? Est-ce que j'ai besoin d'utiliser delou d'attribuer une valeur différente à la variable qui contient l'objet générateur?
ack
1
@davidA Une fois le fichier fermé, les références sont toujours accessibles; Cependant, toute tentative d' utiliser les références aux données traction / poussée vers / à partir du fichier donnera: ValueError: I/O operation on closed file..
RWDJ
36

Oui.

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

..est à peu près équivalent à:

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

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

Plus précisément, la __exit__méthode dans un gestionnaire de contexte est toujours appelée lors de la sortie du bloc (quelles que soient les exceptions, les retours, etc.). La __exit__méthode de l'objet fichier appelle simplement f.close()(par exemple ici en CPython )

dbr
la source
30
Une expérience intéressante pour montrer la garantie que vous obtenez de l' finallykeywrod est: def test(): try: return True; finally: return False.
Ehsan Kia
20

Oui. Plus généralement, la __exit__méthode d'un With Statement Context Manager sera en effet appelée en cas de returnde l'intérieur du contexte. Cela peut être testé avec les éléments suivants:

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

La sortie est:

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

La sortie ci-dessus confirme qu'elle a __exit__été appelée malgré le début return. En tant que tel, le gestionnaire de contexte n'est pas contourné.

Acumenus
la source
4

Oui, mais il peut y avoir des effets secondaires dans d'autres cas, car cela peut faire quelque chose (comme le vidage du tampon) dans le __exit__bloc

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
la source