En Python, si regreso dentro de un bloque "con", ¿se cerrará el archivo?

Respuestas:

238

Sí, actúa como el finallybloque después de un trybloque, es decir, siempre se ejecuta (a menos que el proceso de Python termine de una manera inusual, por supuesto).

También se menciona en uno de los ejemplos de PEP-343, que es la especificación de la withdeclaración:

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

Sin embargo, algo que vale la pena mencionar es que no se pueden detectar fácilmente las excepciones lanzadas por la open()llamada sin poner todo el withbloque dentro de un try..exceptbloque que generalmente no es lo que uno quiere.

ThiefMaster
fuente
8
elsepodría agregarse withpara resolver ese try with exceptproblema. editar: agregado al idioma
rplnt
77
No sé si es relevante, pero que yo sepa Process.terminate()es uno de los pocos (¿el único?) Escenario que no garantiza la llamada de una finallydeclaración: "Tenga en cuenta que los controladores de salida y finalmente las cláusulas, etc., no serán ejecutado."
Rik Poggi
@RikPoggi a os._exitveces se usa: sale del proceso de Python sin llamar a los controladores de limpieza.
Acumenus
2
Quizás burlándose un poco de la serpiente, pero ¿qué withpasa si devuelvo una expresión de generador desde dentro del bloque, la garantía se mantiene mientras el generador siga produciendo valores? durante todo el tiempo que lo haga referencia? Es decir, ¿necesito usar delo asignar un valor diferente a la variable que contiene el objeto generador?
ack
1
@davidA Después de cerrar el archivo, todavía se puede acceder a las referencias; Sin embargo, cualquier intento de utilizar las referencias a los datos de tracción / empuje hacia / desde el archivo dará: ValueError: I/O operation on closed file..
RWDJ
36

Si.

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

..es más o menos equivalente a:

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

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

Más exactamente, el __exit__método en un administrador de contexto siempre se llama al salir del bloque (independientemente de las excepciones, devoluciones, etc.). El __exit__método del objeto de archivo solo llama f.close()(por ejemplo, aquí en CPython )

dbr
fuente
30
Un experimento interesante mostrar la garantía que recibe de la finallykeywrod es: def test(): try: return True; finally: return False.
Ehsan Kia
20

Si. En términos más generales, el __exit__método de un Administrador de contexto de declaración con se llamará en el caso de un returndesde dentro del contexto. Esto se puede probar con lo siguiente:

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

El resultado es:

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

El resultado anterior confirma que __exit__fue llamado a pesar de lo temprano return. Como tal, el administrador de contexto no se omite.

Acumenus
fuente
4

Sí, pero puede haber algún efecto secundario en otros casos, ya que podría hacer algo (como búfer de lavado) en el __exit__bloque

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'
defensor de virus
fuente