2.24. Gestores de contexto

La instrucción with ejecuta código de preparación, luego un cuerpo y después código de limpieza, con la garantía de que la limpieza se ejecuta incluso cuando el cuerpo falla a mitad de camino. El par de métodos que proporciona la preparación y la limpieza se denomina gestor de contexto.

La forma es:

with <expression> as <name>:
    <body>

La expresión devuelve un gestor de contexto. Python llama a su método __enter__, opcionalmente vincula el resultado a <name>, ejecuta el cuerpo y luego llama a __exit__, tanto si el cuerpo se completó con normalidad como si lanzó una excepción.

Diagrama de flujo: __enter__ se ejecuta primero, luego el cuerpo; el cuerpo se completa con normalidad o lanza una excepción; en cualquier caso, __exit__ se ejecuta al final.

__exit__ se ejecuta al final del bloque pase lo que pase dentro de él.

2.24.1. Usar un gestor de contexto

El ejemplo canónico es abrir un archivo:

with open("data.txt") as f:
    text = f.read()

# f is now closed, even if read() failed

open() devuelve un objeto de archivo que es en sí mismo un gestor de contexto; __enter__ devuelve el archivo y __exit__ lo cierra. El bloque with convierte «cerrar siempre el archivo al terminar» en el comportamiento por defecto, en lugar de algo que quien llama tiene que recordar.

2.24.1.1. Múltiples gestores de contexto

Una sola instrucción with puede entrar en varios gestores de contexto a la vez, separados por comas:

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())

Equivalente a dos bloques with anidados, pero más plano. Los gestores se entran de izquierda a derecha y se salen en orden inverso; si __enter__ en el gestor de la derecha lanza una excepción, el __exit__ del gestor de la izquierda se ejecuta igualmente.

2.24.2. Escribir un gestor de contexto

Cualquier clase con métodos __enter__ y __exit__ funciona como gestor de contexto:

class Section:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        print("---", self.label, "---")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("--- end", self.label, "---")
        return False

with Section("setup"):
    print("doing the work")

Salida:

--- setup ---
doing the work
--- end setup ---

__exit__ recibe tres argumentos que describen la excepción que terminó el bloque, o tres valores None si el bloque se completó con normalidad. Devolver False (o None) permite que cualquier excepción se propague tras la limpieza; devolver True la suprimiría.

Usa gestores de contexto para cualquier recurso que tenga un ciclo de vida de «abrir / cerrar» o «adquirir / liberar», no solo archivos. El patrón mantiene la limpieza emparejada con la preparación en el punto donde se introducen ambas, de modo que un cierre olvidado en mitad de una función larga no pueda provocar una fuga del recurso.