contextlib.ExitStack: gestiona recursos dinámicos

Publicado el: 09/09/2026
Tempo de leitura: 4 minutos
Codigo y archivos gestionados con ExitStack en Python

contextlib.ExitStack es una utilidad de la biblioteca estándar de Python para gestionar una colección dinámica de recursos. Funciona como una pila programable de context managers y callbacks de limpieza. En lugar de anidar muchos bloques with, registras cada recurso cuando lo adquieres y dejas que la pila libere todo en orden inverso, incluso cuando una excepción interrumpe la operación.

Qué problema resuelve ExitStack

Un bloque with normal es ideal cuando conoces los recursos de antemano. El problema aparece cuando la cantidad de archivos depende de una lista, una conexión es opcional o una operación de limpieza no implementa el protocolo de context manager.

from contextlib import ExitStack
rutas = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
    archivos = [stack.enter_context(open(r, encoding="utf-8")) for r in rutas]
    textos = [archivo.read() for archivo in archivos]

Todos los archivos se cierran automáticamente. Si falla la lectura de uno, los anteriores también se liberan correctamente. Repasa los fundamentos en la sentencia with en Python y el tratamiento de excepciones.

Cómo funciona la pila de salida

Cada recurso registrado añade una acción de salida. Cuando termina el bloque, las acciones se ejecutan en orden LIFO: la última registrada es la primera que se libera. Es el mismo comportamiento que tendrían varios context managers anidados.

El método más habitual es enter_context. Este llama a __enter__, devuelve el valor producido y guarda __exit__ para ejecutarlo después.

with ExitStack() as stack:
    archivo = stack.enter_context(open("datos.txt", encoding="utf-8"))
    conexion = stack.enter_context(crear_conexion())

Registrar callbacks de limpieza

No todos los recursos implementan un context manager. El método callback registra una función normal y sus argumentos para ejecutarla cuando se cierre la pila.

from pathlib import Path
from contextlib import ExitStack
carpeta = Path("temporal")
carpeta.mkdir(exist_ok=True)
with ExitStack() as stack:
    stack.callback(carpeta.rmdir)
    salida = carpeta / "resultado.txt"
    salida.write_text("listo", encoding="utf-8")
    stack.callback(salida.unlink)

La eliminación del archivo fue registrada al final y se ejecuta primero. Después se elimina la carpeta vacía. Para trabajar con rutas, consulta pathlib en Python.

El método push

push registra directamente un método __exit__ o una función compatible. A diferencia de enter_context, no llama a __enter__. Es útil cuando el recurso ya fue iniciado y solo quieres transferir la responsabilidad de salida.

recurso = Recurso()
recurso.iniciar()
with ExitStack() as stack:
    stack.push(recurso)
    recurso.ejecutar()

Esta diferencia evita inicializaciones duplicadas y debe quedar documentada para que el código sea fácil de mantener.

Recursos opcionales

ExitStack permite adquirir recursos de forma condicional sin duplicar todo el bloque. Puedes abrir un log, iniciar una transacción o adquirir un bloqueo únicamente cuando sea necesario.

with ExitStack() as stack:
    if depuracion:
        log = stack.enter_context(open("debug.log", "a", encoding="utf-8"))
        log.write("inicio\n")
    ejecutar_tarea()

También puedes combinar la pila con nullcontext cuando necesitas una expresión uniforme.

Procesar varios archivos

Un caso frecuente es combinar una cantidad variable de archivos CSV. ExitStack mantiene abiertos todos los streams durante el procesamiento y los cierra de forma fiable al terminar.

import csv
from contextlib import ExitStack

def combinar(rutas):
    with ExitStack() as stack:
        archivos = [stack.enter_context(open(r, newline="", encoding="utf-8")) for r in rutas]
        lectores = [csv.DictReader(a) for a in archivos]
        return [fila for lector in lectores for fila in lector]

Amplía el tema en archivos CSV en Python.

Separar adquisición y confirmación

El método pop_all transfiere los callbacks registrados a una nueva pila sin ejecutarlos. Esto permite crear operaciones en dos fases: primero adquirir y validar; después decidir quién conserva la responsabilidad de limpieza.

stack = ExitStack()
try:
    recursos = [stack.enter_context(open(r, encoding="utf-8")) for r in rutas]
    validar(recursos)
    propietario = stack.pop_all()
finally:
    stack.close()

Este patrón es útil en constructores complejos, importaciones por lotes y procesos que deben deshacer todo cuando una etapa intermedia falla.

Comportamiento ante excepciones

Los callbacks registrados con callback no reciben datos de la excepción y no pueden suprimirla. Las funciones de salida registradas con push reciben el tipo, el valor y el traceback, y pueden suprimir la excepción devolviendo un valor verdadero.

La supresión silenciosa debe usarse con cuidado porque puede ocultar errores. Las funciones de limpieza deben ser pequeñas, previsibles e idempotentes cuando sea posible. Evita agrupar tareas no relacionadas en un único callback.

ExitStack en pruebas

Las pruebas automatizadas suelen necesitar patches, archivos temporales y mocks cuyo número cambia según el caso. ExitStack mantiene estos recursos dentro de un único alcance visible.

from contextlib import ExitStack
from unittest.mock import patch
with ExitStack() as stack:
    mock_a = stack.enter_context(patch("modulo.funcion_a"))
    mock_b = stack.enter_context(patch("modulo.funcion_b"))
    ejecutar_flujo()

Consulta también pruebas unitarias en Python.

AsyncExitStack

Para aplicaciones asíncronas existe contextlib.AsyncExitStack. Incluye enter_async_context, push_async_exit y push_async_callback. Las operaciones de salida se esperan con await.

from contextlib import AsyncExitStack
async with AsyncExitStack() as stack:
    sesion = await stack.enter_async_context(crear_sesion())
    canal = await stack.enter_async_context(abrir_canal())

Resulta útil con clientes de red, bases de datos, colas y servicios asíncronos. Consulta asyncio en Python.

Buenas prácticas

Usa ExitStack cuando la cantidad de recursos sea dinámica, existan recursos opcionales o necesites combinar context managers con callbacks normales. Para dos o tres recursos fijos, un with tradicional suele ser más claro.

Registra la limpieza inmediatamente después de adquirir cada recurso. Mantén los callbacks pequeños. Documenta el uso de push, ya que no ejecuta __enter__. No uses ExitStack como sustituto de validación, logs, reintentos o mensajes de error. Su responsabilidad principal es gestionar el ciclo de vida.

Conclusión

contextlib.ExitStack convierte la liberación de recursos en un flujo flexible y explícito. Conserva la seguridad del comando with, pero admite decisiones en tiempo de ejecución, callbacks comunes, recursos opcionales y transferencia de propiedad. Es especialmente útil en scripts de automatización, procesamiento de archivos, pruebas y servicios que coordinan varios recursos externos.

Referencias oficiales: documentación de ExitStack y PEP 343.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Desarrollador creando plantillas de texto con string.Template en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    string.Template: plantillas de texto simples y seguras

    Aprende string.Template en Python para crear textos configurables, validar campos y sustituir valores de forma clara y segura.

    Ler mais

    Tempo de leitura: 6 minutos
    09/09/2026
    Equipo sincronizado representando asyncio.Barrier en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    asyncio.Barrier: sincroniza tareas por fases

    Aprende asyncio.Barrier en Python para sincronizar tareas por fases, coordinar pipelines y gestionar cancelaciones con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    08/09/2026
    Desarrollador creando modelos con dataclasses.KW_ONLY en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    dataclasses.KW_ONLY: exige argumentos con nombre

    Aprende dataclasses.KW_ONLY en Python para exigir argumentos con nombre, evitar llamadas ambiguas y evolucionar APIs con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    08/09/2026
    Desarrollador usando operator.methodcaller en código Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    operator.methodcaller: llama métodos en pipelines

    Aprende operator.methodcaller en Python para map, sorted, callbacks, argumentos y pipelines declarativos claros y reutilizables.

    Ler mais

    Tempo de leitura: 5 minutos
    07/09/2026
    Carpetas y directorios recorridos con pathlib.Path.walk en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pathlib.Path.walk: recorre y filtra directorios

    Aprende a recorrer directorios con pathlib.Path.walk en Python, filtrar archivos, omitir carpetas y evitar errores comunes.

    Ler mais

    Tempo de leitura: 6 minutos
    07/09/2026
    Código Python validado con enum.verify
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    enum.verify: valida reglas de Enum en Python

    Aprende enum.verify en Python para validar valores únicos, secuencias continuas y flags con nombres mediante reglas explícitas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/09/2026