La historia — en Python, para la gestión de recursos (archivos, conexiones, transacciones) se utiliza la construcción with, basada en el protocolo de administradores de contexto (enter, exit). Para casos simples, escribir toda una clase es redundante, por lo que se propuso el decorador @contextmanager (módulo contextlib), que permite definir administradores de recursos como generadores.
Problema — liberar o cerrar recursos manualmente es engorroso, el código se vuelve inestable a errores (por ejemplo, olvidar cerrar un archivo). También no se quiere escribir una clase separada con dos métodos para cosas simples (como cambiar temporalmente un directorio o stdout).
Solución — usar @contextmanager para describir de manera concisa el "inicio" y el "fin" del uso del recurso, garantizando el manejo de excepciones y la liberación.
Ejemplo de código:
from contextlib import contextmanager @contextmanager def open_file(filename, mode): f = open(filename, mode) try: yield f finally: f.close() with open_file('test.txt', 'w') as f: f.write('Hello')
Características clave:
¿Es posible hacer que yield de @contextmanager devuelva múltiples objetos (por ejemplo, a través de una tupla)?
Sí, es posible y conveniente para devolver un "grupo" de recursos relacionados.
Ejemplo de código:
@contextmanager def managed_two(): a, b = [], {} try: yield a, b finally: a.clear(); b.clear()
¿Qué sucederá si se lanza una excepción después de yield — se cerrará el recurso?
Sí, el bloque finally se ejecutará en cualquier caso, incluso si se produce un error/excepción dentro del código dentro de with.
¿Puede @contextmanager reemplazar una clase completa de administrador de contexto con enter/exit?
En la mayoría de los casos triviales — sí, para casos más complejos con estados anidados o herencia, es más cómodo trabajar a través de una clase.
Abren y cierran un archivo manualmente:
f = open('test.txt', 'w') try: f.write('Hello') finally: f.close()
Ventajas:
Desventajas:
Usan @contextmanager para cambiar temporalmente el directorio de trabajo o abrir un archivo (o configuraciones de entorno):
@contextmanager def work_in(dirname): import os prev = os.getcwd() os.chdir(dirname) try: yield finally: os.chdir(prev)
Ventajas:
Desventajas: