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 delas)__exit__: limpia SIEMPRE. Si hubo excepción, llega en los parámetrosreturn Trueen__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
withgarantiza el cleanup incluso con excepciones- Clase:
__enter__(setup) +__exit__(cleanup, corre siempre) @contextmanager+yield: la versión corta; cleanup enfinally- Úsalo para todo recurso con ciclo de vida: archivos, conexiones, locks, timers