contextlib en Python: gestiona recursos

Publicado el: 27/08/2026
Tempo de leitura: 7 minutos
A top view of stacked timber logs showcasing natural textures and patterns.

El módulo contextlib ofrece herramientas para crear y combinar context managers. Controlan la entrada y salida de un bloque with, garantizando cleanup de archivos, sockets, locks, transacciones, directorios temporales y otros recursos incluso cuando ocurre una excepción. El módulo reduce clases repetitivas y hace explícitos el lifecycle y el manejo de errores.

Un context manager no sirve únicamente para cerrar objetos. Puede configurar estado temporal, registrar métricas, redirigir streams, iniciar y finalizar transacciones o componer una cantidad dinámica de recursos. La regla central es que cada adquisición exitosa tenga una liberación correspondiente y predecible.

El protocolo de contexto

Un context manager implementa __enter__() y __exit__(). El valor devuelto por __enter__() se asigna al objetivo de as. __exit__() recibe información de la excepción cuando el bloque falla.

class Recurso:
    def __enter__(self):
        self.abrir()
        return self

    def __exit__(self, tipo, valor, traceback):
        self.cerrar()
        return False

Devolver un valor verdadero desde __exit__() suprime la excepción. Hazlo solo cuando el error haya sido tratado realmente.

contextmanager

El decorator @contextmanager convierte una función generator en context manager. El código anterior a yield se ejecuta al entrar; el cleanup dentro de finally, al salir.

from contextlib import contextmanager

@contextmanager
def conexion_temporal():
    conexion = abrir_conexion()
    try:
        yield conexion
    finally:
        conexion.close()

with conexion_temporal() as conexion:
    conexion.ejecutar()

El generator debe producir exactamente una vez. No alcanzar el yield o producir varias veces rompe el protocolo.

Coloca cleanup en finally

Sin finally, una excepción lanzada por el cuerpo puede saltar la liberación.

@contextmanager
def archivo_bloqueado(ruta):
    archivo = open(ruta, "a+")
    adquirir_lock(archivo)
    try:
        yield archivo
    finally:
        liberar_lock(archivo)
        archivo.close()

Si la adquisición puede fallar a mitad, libera solamente recursos realmente adquiridos.

Excepciones dentro del generator

Cuando el cuerpo lanza una excepción, se inyecta en el punto de yield. El generator puede registrarla, convertirla o tratarla.

@contextmanager
def registrar_fallos(logger):
    try:
        yield
    except Exception:
        logger.exception("fallo en el bloque")
        raise

Vuelve a lanzar cuando el problema no haya sido resuelto. Registrar y continuar puede ocultar estado corrupto.

ContextDecorator

Los context managers basados en ContextDecorator también pueden decorar funciones.

from contextlib import ContextDecorator

class Cronometro(ContextDecorator):
    def __enter__(self):
        self.inicio = ahora()
        return self

    def __exit__(self, *exc):
        registrar_duracion(ahora() - self.inicio)
        return False

@Cronometro()
def procesar():
    ejecutar_tarea()

El objeto debe soportar uso repetido cuando la función decorada pueda llamarse varias veces.

closing

closing(objeto) llama a close() al salir. Es útil para objetos legacy que tienen cierre, pero no implementan el protocolo.

from contextlib import closing

with closing(abrir_recurso_legacy()) as recurso:
    recurso.usar()

No envuelvas un objeto que ya soporta with sin necesidad. Su manager nativo puede ejecutar pasos adicionales.

aclosing

aclosing() es la versión asíncrona para objetos con aclose(), especialmente generators async.

from contextlib import aclosing

async with aclosing(stream_asincrono()) as stream:
    async for item in stream:
        if item.listo:
            break

El cleanup ocurre en el mismo contexto asíncrono, conservando context variables, excepciones y lifecycle de la tarea.

asynccontextmanager

@asynccontextmanager crea context managers async a partir de async generators.

from contextlib import asynccontextmanager

@asynccontextmanager
async def cliente_api():
    cliente = await crear_cliente()
    try:
        yield cliente
    finally:
        await cliente.aclose()

Úsalo con async with. El cleanup puede esperar I/O, pero necesita timeouts y cancelación correctos.

Managers reutilizables y reentrantes

Algunos managers son single-use, otros reutilizables y otros reentrantes. Son propiedades diferentes.

Un manager basado en generator crea una instancia nueva cada vez que llamas la función decorada; usa with recurso(): y no almacenes una instancia para múltiples entradas.

nullcontext

nullcontext() no realiza cleanup y devuelve un valor opcional. Simplifica caminos donde el recurso quizá ya esté abierto.

from contextlib import nullcontext

contexto = open(ruta) if ruta else nullcontext(stream_existente)
with contexto as stream:
    procesar(stream)

Evita duplicar el cuerpo del with en dos branches.

suppress

suppress(*excepciones) ignora tipos seleccionados.

from contextlib import suppress

with suppress(FileNotFoundError):
    ruta.unlink()

Úsalo solo cuando la excepción represente un resultado esperado y aceptable. No suprimas Exception ampliamente.

Suprimir no es registrar

Si un error requiere auditoría, retry, métrica o decisión visible, un try/except explícito es más claro. suppress comunica que la falta de efecto es aceptable y silenciosa.

redirect_stdout

redirect_stdout(destino) reemplaza temporalmente sys.stdout.

from contextlib import redirect_stdout
from io import StringIO

buffer = StringIO()
with redirect_stdout(buffer):
    funcion_que_imprime()
texto = buffer.getvalue()

El cambio es global al proceso y afecta otras threads. Úsalo principalmente en scripts, tests controlados y herramientas single-thread.

redirect_stderr

redirect_stderr() hace lo mismo con sys.stderr. No captura escrituras directas a descriptores nativos, subprocesses independientes o logging configurado en otro destino.

Para procesos hijos, usa las opciones de captura de subprocess.

chdir temporal

chdir(ruta) cambia el directorio actual durante el bloque y lo restaura al salir.

from contextlib import chdir

with chdir("proyecto"):
    ejecutar_build()

El directorio actual es estado global. No uses este patrón en programas con threads o tareas concurrentes que dependan de paths relativos.

ExitStack

ExitStack compone una cantidad dinámica de context managers y callbacks.

from contextlib import ExitStack

with ExitStack() as stack:
    archivos = [
        stack.enter_context(open(ruta, encoding="utf-8"))
        for ruta in rutas
    ]
    combinar(archivos)

Si abrir el tercer archivo falla, los anteriores se cierran automáticamente.

Orden LIFO

ExitStack ejecuta callbacks en orden inverso a la adquisición. Esto encaja con recursos dependientes: el más reciente se libera primero.

Registra cleanup inmediatamente después de cada adquisición para no crear una ventana de fuga.

callback

stack.callback(funcion, *args, **kwargs) registra una llamada de cleanup que no recibe información de la excepción.

with ExitStack() as stack:
    directorio = crear_directorio_temporal()
    stack.callback(eliminar_arbol, directorio)
    ejecutar(directorio)

Haz callbacks idempotentes cuando sea posible, porque el cleanup puede encontrar estado parcial.

push

push() registra la parte de salida de un context manager o una función compatible con __exit__. A diferencia de callback, puede observar y suprimir excepciones.

Usa esa capacidad con cuidado, porque cambia lo que ven callbacks exteriores y callers.

enter_context

enter_context(cm) llama a __enter__() y registra __exit__(). Devuelve el valor que recibiría un objetivo as.

Así resulta sencillo construir recursos definidos en runtime.

pop_all

pop_all() transfiere callbacks a otra stack sin ejecutarlos. Sirve para adquisición “todo o nada”.

stack = ExitStack()
try:
    recursos = [stack.enter_context(abrir(x)) for x in items]
except Exception:
    stack.close()
    raise
else:
    stack_final = stack.pop_all()

Después de transferir, el nuevo owner debe cerrar la stack.

AsyncExitStack

AsyncExitStack combina context managers síncronos, async y callbacks de cleanup async.

from contextlib import AsyncExitStack

async with AsyncExitStack() as stack:
    clientes = [
        await stack.enter_async_context(crear_cliente(url))
        for url in urls
    ]
    await consultar(clientes)

Es especialmente útil cuando la cantidad de conexiones asíncronas es dinámica.

push_async_callback

Los callbacks asíncronos pueden esperar flush, shutdown o liberación. También ejecutan en orden inverso.

Aplica deadlines, porque un cleanup trabado puede impedir el cierre de la aplicación.

Transacciones

Un manager de transacción puede hacer commit tras completar normalmente y rollback ante una excepción.

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

No ocultes el fallo original después del rollback sin otro canal de estado explícito.

Adquisición parcial

Cuando setup tiene varias etapas, usa una ExitStack interna para registrar cada cleanup. Transfiere la stack solo cuando todas las etapas hayan tenido éxito.

Este patrón sustituye flags booleanas y finally anidados.

Excepciones durante cleanup

Una excepción de cleanup puede sustituir o encadenarse con el error original. Usa tipos específicos, logging y chaining explícito para conservar diagnóstico.

No ignores un commit, flush o close fallido cuando garantiza durabilidad.

Varios managers en un with

Para una cantidad fija, un único with con varios managers es más simple.

with abrir_a() as a, abrir_b() as b:
    usar(a, b)

Usa ExitStack cuando cantidad o tipos sean dinámicos.

Context managers y sockets

Los sockets ya soportan el protocolo y se cierran al salir.

import socket

with socket.create_connection((host, puerto), timeout=5) as sock:
    sock.sendall(datos)

Consulta socket en Python para timeouts, framing y shutdown.

Context managers y e-mail

Los clientes smtplib.SMTP también soportan with, garantizando cierre ordenado. Consulta smtplib en Python.

Locks

Locks de threading y multiprocessing funcionan como context managers. El bloque reduce el riesgo de olvidar liberar.

with lock:
    actualizar_estado()

Todavía debes evitar deadlock, mantener corta la sección crítica y adquirir varios locks en orden consistente.

Context variables

Un manager puede configurar temporalmente una ContextVar y restaurar su token al salir.

@contextmanager
def contexto_request(valor):
    token = request_id.set(valor)
    try:
        yield
    finally:
        request_id.reset(token)

Es útil para logging y tracing, también en código async.

Métricas

Un manager puede medir duración y registrar éxito o fallo al salir mientras preserva la excepción.

No permitas que una falla del backend de observabilidad oculte el error principal.

Tipado

Usa typing.ContextManager, AsyncContextManager o protocolos estructurales para declarar APIs. Anota el tipo producido por yield.

Una firma clara distingue el objeto manager del recurso que devuelve.

Pruebas

Prueba salida normal, excepción en el cuerpo, fallo de adquisición, fallo de cleanup, uso repetido, cancelación async y adquisición parcial. Verifica el orden de liberación.

Los mocks deben confirmar que close, rollback y callbacks ocurren exactamente cuando corresponde.

Errores comunes

Los fallos frecuentes son olvidar finally, producir varias veces en @contextmanager, suprimir excepciones accidentalmente, redirigir stdout en un proceso multithread, cambiar el directorio global concurrentemente, reutilizar un manager single-use, registrar cleanup demasiado tarde y olvidar cerrar una stack transferida.

Conclusión

contextlib hace más seguras y claras la adquisición y liberación. Usa @contextmanager para managers simples, ExitStack para recursos dinámicos, variantes async para cleanup esperable y nullcontext para caminos opcionales.

Mantén ownership explícito y no ocultes errores importantes. Consulta la documentación oficial de contextlib y la referencia del protocolo de context manager.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Close-up of a computer screen displaying colorful programming code with depth of field.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ast en Python: analiza código fuente

    Aprende ast en Python para analizar y transformar código, crear visitors, conservar posiciones, usar literal_eval y evitar riesgos de ejecución.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    Close-up of electric plug and socket with vibrant lighting, showcasing technology and energy concepts.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    socket en Python: redes TCP y UDP

    Aprende socket en Python para clientes y servidores TCP y UDP, framing, timeouts, IPv6, concurrencia, TLS y seguridad de red.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    multiprocessing en Python: varios núcleos

    Aprende multiprocessing en Python con procesos, pools, queues, pipes, memoria compartida, cancelación, seguridad y shutdown correcto.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    Bright yellow and blue shopping carts arranged in orderly rows outdoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    concurrent.futures: hilos y procesos en paralelo

    Aprende concurrent.futures en Python con threads, procesos, Future, timeouts, cancelación, backpressure y prevención de deadlocks.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    winsound en Python: audio en Windows

    Aprende winsound en Python para reproducir WAV, sonidos del sistema, beeps, loops y notificaciones asíncronas de forma segura en Windows.

    Ler mais

    Tempo de leitura: 6 minutos
    26/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    winreg en Python: Registro de Windows

    Aprende winreg en Python para leer y escribir el Registro de Windows, gestionar tipos, permisos, vistas WOW64, eliminaciones y seguridad.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026