PythonAprende PythonDocumentación

Context managers

El with: setup y teardown garantizados, incluso con errores.

Ya usaste with open(...): garantiza que el archivo se cierre, pase lo que pase. Ese patrón —preparar, usar, limpiar siempre— es el context manager, y puedes escribir los tuyos.

El problema que resuelve

archivo = open("datos.txt")
procesar(archivo)          # si aquí explota una excepción...
archivo.close()            # ...nunca se ejecuta → recurso colgado
with open("datos.txt") as archivo:   # close() garantizado
    procesar(archivo)                # incluso si explota

with llama al cleanup incluso con excepciones. Es una garantía estructural, no una promesa.

La anatomía: enter y exit

class Temporizador:
    def __enter__(self):
        self.inicio = time.perf_counter()
        return self            # lo que recibe la variable del "as"

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.duracion = time.perf_counter() - self.inicio
        print(f"Tomó {self.duracion:.3f}s")
        return False           # False: no tragar excepciones

with Temporizador() as t:
    sum(range(1_000_000))
# Tomó 0.012s
  • __enter__: prepara y devuelve el recurso (lo del as)
  • __exit__: limpia SIEMPRE. Si hubo excepción, llega en los parámetros
  • return True en __exit__ suprimiría la excepción — casi nunca lo quieres

La versión corta: @contextmanager

Para context managers simples, contextlib convierte un generador en CM — sin clase:

from contextlib import contextmanager

@contextmanager
def abrir_recurso(nombre):
    print(f"abriendo {nombre}")     # __enter__: antes del yield
    try:
        yield nombre                # el cuerpo del with ejecuta aquí
    finally:
        print(f"cerrando {nombre}") # __exit__: después del yield

with abrir_recurso("db") as r:
    print("usando", r)
# abriendo db
# usando db
# cerrando db

El yield divide la función en dos: lo de antes es el setup, lo de después (en finally) es el cleanup.

Combinar varios recursos

with open("entrada.txt") as origen, open("salida.txt", "w") as destino:
    destino.write(origen.read())

Varios context managers en una línea, todos con su cleanup garantizado.

Errores comunes

# Olvidar el yield en @contextmanager → RuntimeError
# Olvidar finally → cleanup no garantizado si hay excepción
# return True en __exit__ → traga excepciones silenciosamente

Resumen

  • with garantiza el cleanup incluso con excepciones
  • Clase: __enter__ (setup) + __exit__ (cleanup, corre siempre)
  • @contextmanager + yield: la versión corta; cleanup en finally
  • Úsalo para todo recurso con ciclo de vida: archivos, conexiones, locks, timers

Quiz

  1. 1. ¿Qué garantiza el bloque `with`?

  2. 2. ¿Qué dos métodos definen un context manager como clase?

  3. 3. ¿Qué logra @contextmanager de contextlib sobre un generador?

Ejercicios

Ejercicio 1: Context manager con clase

Crea la clase `Administrador` cuyos __enter__ imprima abriendo y __exit__ imprima cerrando. Úsala con with para imprimir trabajando dentro. Resultado: abriendo / trabajando / cerrando.

Cargando editor…

Ejercicio 2: Con contextlib

Logra la misma traza (iniciando / tarea / terminando) usando @contextmanager de contextlib en una función generadora.

Cargando editor…