ProgramaciónDesarrollador backend de Python

Hable sobre el trabajo con administradores de contexto y administradores de recursos a través del decorador @contextmanager del módulo contextlib: ¿para qué sirve, cómo funciona y cuáles son las trampas?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

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:

  • Inicio del bloque — todo antes de yield, que devuelve el recurso.
  • Fin del bloque — después de yield; debe incluir el manejo de errores y el cierre/liberación.
  • Garantía de cierre del recurso incluso si ocurre una excepción.

Preguntas trampa.

¿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.

Errores comunes y anti-patrones

  • Omitir el bloque finally, lo que conduce a fugas de recursos en caso de errores.
  • Yield no es único; si la función tiene dos yield — esto provocará un RuntimeError.
  • Intentar usar @contextmanager con funciones que no son generadores (sin yield).

Ejemplo de la vida real

Caso negativo

Abren y cierran un archivo manualmente:

f = open('test.txt', 'w') try: f.write('Hello') finally: f.close()

Ventajas:

  • Control total sobre la gestión del ciclo de vida del recurso.

Desventajas:

  • Mucho código de plantilla, mayor posibilidad de error al olvidarse del finally.

Caso positivo

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:

  • Concisión, garantía de retorno al estado original.

Desventajas:

  • Un alto nivel de abstracción puede ocultar los detalles de la gestión del recurso.