Los gestores de contexto delimitan la adquisición y liberación de recursos. contextlib permite expresar esa garantía con menos código cuando un bloque try/finally sería correcto, pero repetitivo.

Crear un contexto con un generador

from contextlib import contextmanager
from pathlib import Path
from typing import Iterator, TextIO


@contextmanager
def abrir_informe(ruta: Path) -> Iterator[TextIO]:
    archivo = ruta.open("w", encoding="utf-8")
    try:
        yield archivo
    finally:
        archivo.close()


with abrir_informe(Path("informe.txt")) as informe:
    informe.write("proceso terminado\n")

El valor entregado por yield aparece después de as. Un generador decorado con @contextmanager debe ejecutar exactamente un yield. Si la preparación falla antes, el cuerpo no comienza. Si falla el cuerpo, la excepción reaparece en la expresión yield, donde puede registrarse, convertirse o propagarse.

@contextmanager
def transaccion(conexion):
    cursor = conexion.cursor()
    try:
        yield cursor
    except Exception:
        conexion.rollback()
        raise
    else:
        conexion.commit()
    finally:
        cursor.close()

El else confirma solo después de un cuerpo exitoso; un error revierte y el cursor siempre se cierra. Capturar sin volver a lanzar indica a with que la excepción fue resuelta. Puede ser intencional en un caso estrecho y documentado, pero es un valor predeterminado peligroso.

Usar adaptadores con precisión

closing(recurso) llama a close() al salir. Úsalo solo si un objeto necesita cerrarse y aún no implementa el protocolo. Los archivos y muchos recursos estándar ya funcionan directamente con with.

nullcontext(valor) no limpia nada y entrega el propio valor. Sirve cuando el llamador puede proporcionar un recurso opcional:

from contextlib import nullcontext


def leer_lineas(origen=None):
    contexto = (
        open("entrada.txt", encoding="utf-8")
        if origen is None
        else nullcontext(origen)
    )
    with contexto as archivo:
        return archivo.readlines()

suppress(FileNotFoundError) expresa que una ausencia específica es aceptable, por ejemplo al borrar una caché opcional. Mantén corto el bloque protegido. Una supresión amplia convierte errores ajenos en falso éxito.

redirect_stdout() y redirect_stderr() sustituyen temporalmente streams globales. Ayudan a capturar código que no recibe un stream, sobre todo en pruebas, pero no son adecuados para concurrencia o bibliotecas. Cuando controles la función, prefiere aceptar un destino o utilizar logging estructurado.

Componer recursos dinámicos con ExitStack

Los contextos fijos caben en una instrucción with. ExitStack aporta valor cuando la cantidad se decide durante la ejecución o hay que combinar callbacks de limpieza.

from contextlib import ExitStack
from pathlib import Path


def concatenar(rutas: list[Path], destino: Path) -> None:
    with ExitStack() as stack:
        entradas = [
            stack.enter_context(p.open(encoding="utf-8"))
            for p in rutas
        ]
        salida = stack.enter_context(destino.open("w", encoding="utf-8"))
        for entrada in entradas:
            salida.write(entrada.read())

Si falla la apertura del tercer archivo, los dos primeros ya están registrados y se cierran automáticamente. La limpieza ocurre en orden inverso, igual que en bloques anidados. stack.callback(funcion, *args) registra una función común; pop_all() transfiere los callbacks cuando la propiedad debe cambiar después de validar.

No uses ExitStack para ocultar dos recursos estables: un with directo resulta más legible. La abstracción compensa cuando la adquisición es condicional, iterativa o mezcla callbacks.

Contextos asíncronos y pruebas

Trata la limpieza como parte del contrato público. Documenta el recurso adquirido, el valor entregado, las excepciones convertidas y cualquier supresión intencional. Registra la limpieza inmediatamente después de cada adquisición, porque un fallo durante la preparación también debe liberar lo obtenido. Si el cierre puede fallar, decide qué error debe permanecer visible.

Utiliza en pruebas un recurso falso que registre llamadas. Comprueba el orden abrir, usar y cerrar; lanza una excepción dentro del cuerpo y confirma el cierre. Con ExitStack, falla a mitad de la adquisición y verifica que los elementos anteriores se liberen en orden inverso. Para transacciones, prueba commit y rollback por separado.

No envuelvas cálculo normal en un contexto solo por simetría. El protocolo funciona mejor cuando representa propiedad o cambio temporal con restauración clara. Prueba también un retorno anticipado, porque salir del cuerpo mediante return todavía debe limpiar. En código concurrente, evita utilidades que modifican estado global.

asynccontextmanager aplica la misma estructura de un yield a async with. AsyncExitStack combina gestores asíncronos y callbacks que esperan corutinas. Elígelos cuando adquirir o liberar requiera realmente await, no solo porque la función llamadora sea asíncrona.

Cada instancia producida por el generador es de un solo uso; la función decorada crea otra en cada llamada. Si el contexto debe ser reentrante o exponer bastante estado, una clase con __enter__ y __exit__ puede mostrar mejor las transiciones.

Prueba el camino normal, una excepción dentro del cuerpo y una adquisición parcial con pilas. La limpieza debería conservar el error original cuando sea posible, porque una excepción nueva durante close() puede ocultar la causa inicial.

El código anterior a yield adquiere el recurso y finally garantiza la limpieza aunque el cuerpo de with falle. No ocultes errores sin una política explícita: registrar y volver a lanzar suele ser más seguro que simular éxito.

closing() adapta objetos con close() que no implementan el protocolo de contexto. suppress() solo conviene para excepciones esperadas y específicas. Evita suppress(Exception), porque puede esconder defectos.

Cuando los recursos llegan en una lista, ExitStack entra en contextos durante un bucle y los deshace en orden inverso. Es más seguro que mantener manualmente archivos abiertos.

La guía de manejo de excepciones en Python completa esta estrategia. La documentación oficial de contextlib, consultada el 22 de julio de 2026, explica contextos síncronos, asíncronos y ExitStack.