contextlib.ExitStack: gerencie recursos dinâmicos

Publicado em: 09/09/2026
Tempo de leitura: 5 minutos
Código e estrutura de arquivos gerenciados com contextlib.ExitStack no Python

contextlib.ExitStack é uma ferramenta da biblioteca padrão do Python criada para gerenciar recursos cujo número ou tipo só é conhecido em tempo de execução. Ela funciona como uma pilha de context managers e callbacks de limpeza. Em vez de escrever vários blocos with aninhados, você registra recursos em uma pilha e garante que todos sejam liberados na ordem inversa, mesmo quando ocorre uma exceção.

O problema que ExitStack resolve

O comando with é excelente quando você sabe antecipadamente quais recursos serão usados. Por exemplo, abrir dois arquivos é simples com with open(...), open(...). O problema aparece quando a quantidade de arquivos depende de uma lista, quando alguns recursos são opcionais ou quando parte da limpeza não vem de um context manager tradicional.

from contextlib import ExitStack

caminhos = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
    arquivos = [stack.enter_context(open(c, encoding="utf-8")) for c in caminhos]
    conteudos = [arquivo.read() for arquivo in arquivos]

Ao sair do bloco, todos os arquivos são fechados automaticamente. Isso também acontece se a leitura do segundo arquivo falhar. Para revisar o funcionamento básico de contextos, veja como usar with no Python e tratamento de erros com try except.

Como a pilha de saída funciona

Cada recurso registrado adiciona uma ação de saída. Quando o bloco termina, as ações são executadas em ordem LIFO: o último recurso registrado é o primeiro a ser liberado. Esse comportamento reproduz o encerramento natural de blocos with aninhados.

O método mais usado é enter_context. Ele recebe um context manager, chama seu método __enter__, devolve o valor produzido e registra o __exit__ para execução posterior.

with ExitStack() as stack:
    arquivo = stack.enter_context(open("dados.txt", encoding="utf-8"))
    conexao = stack.enter_context(criar_conexao())
    # ambos serão encerrados corretamente

Registrando callbacks de limpeza

Nem todo recurso implementa o protocolo de context manager. Para esses casos, use callback. O método recebe uma função e seus argumentos, executando-a quando a pilha for fechada.

from pathlib import Path
from contextlib import ExitStack

pasta = Path("temporario")
pasta.mkdir(exist_ok=True)

with ExitStack() as stack:
    stack.callback(lambda: pasta.rmdir())
    arquivo = pasta / "saida.txt"
    arquivo.write_text("resultado", encoding="utf-8")
    stack.callback(arquivo.unlink)

Como a remoção do arquivo foi registrada depois da remoção da pasta, ela será executada primeiro. Isso evita tentar apagar uma pasta ainda ocupada.

O método push

O método push registra diretamente uma função compatível com __exit__ ou um objeto que implemente esse método. Ele é útil quando você já iniciou parte de uma operação e deseja transferir apenas a responsabilidade de saída para a pilha.

recurso = MeuRecurso()
recurso.iniciar()
with ExitStack() as stack:
    stack.push(recurso)
    recurso.executar()

Diferentemente de enter_context, push não chama __enter__. Essa diferença é importante para evitar inicialização duplicada.

Recursos opcionais

Uma vantagem prática é adicionar recursos condicionalmente sem duplicar blocos. Um programa pode abrir um arquivo de log apenas quando o usuário habilita depuração.

from contextlib import ExitStack, nullcontext

with ExitStack() as stack:
    log = stack.enter_context(
        open("debug.log", "a", encoding="utf-8") if modo_debug else nullcontext(None)
    )
    if log:
        log.write("execução iniciada\n")

Outra opção é simplesmente chamar enter_context dentro de um if. Para caminhos e arquivos, consulte também pathlib no Python.

Validação antes de confirmar

ExitStack ajuda a implementar operações em duas fases. Primeiro você adquire recursos e valida condições. Depois decide se mantém ou desfaz as ações. O método pop_all transfere os callbacks para uma nova pilha sem executá-los.

stack = ExitStack()
try:
    arquivos = [stack.enter_context(open(c, encoding="utf-8")) for c in caminhos]
    validar(arquivos)
    nova_pilha = stack.pop_all()
finally:
    stack.close()

Esse padrão é útil para construtores complexos, importações de dados e fluxos em que uma falha parcial deve desfazer tudo.

Exemplo com vários arquivos

Imagine um utilitário que combina diversos arquivos CSV. A lista pode variar conforme a entrada do usuário. Com ExitStack, o código permanece curto e seguro.

import csv
from contextlib import ExitStack

def combinar(caminhos):
    with ExitStack() as stack:
        arquivos = [stack.enter_context(open(c, newline="", encoding="utf-8")) for c in caminhos]
        leitores = [csv.DictReader(a) for a in arquivos]
        return [linha for leitor in leitores for linha in leitor]

Para aprofundar o formato, veja arquivos CSV no Python.

Tratamento de exceções

Os callbacks registrados por callback não recebem informações da exceção e não podem suprimi-la. Já funções registradas com push seguem a assinatura de __exit__ e podem observar ou suprimir exceções. Em aplicações comuns, é melhor evitar supressão silenciosa, pois ela pode esconder erros importantes.

Se uma ação de limpeza falhar, as demais ainda podem ser executadas, mas a exceção final pode mudar conforme a sequência de callbacks. Por isso, cada etapa de limpeza deve ser pequena, previsível e idempotente quando possível.

ExitStack em testes

Em testes automatizados, a classe facilita registrar patches, arquivos temporários e objetos simulados de forma dinâmica. Isso reduz a necessidade de vários decoradores e deixa claro quais recursos pertencem ao teste.

from contextlib import ExitStack
from unittest.mock import patch

with ExitStack() as stack:
    mock_a = stack.enter_context(patch("modulo.funcao_a"))
    mock_b = stack.enter_context(patch("modulo.funcao_b"))
    executar_fluxo()

Para boas práticas de testes, consulte testes unitários no Python.

AsyncExitStack

Para recursos assíncronos existe contextlib.AsyncExitStack. Ela oferece métodos como enter_async_context, push_async_exit e push_async_callback. O conceito é o mesmo, mas a saída é aguardada com await.

from contextlib import AsyncExitStack

async with AsyncExitStack() as stack:
    sessao = await stack.enter_async_context(criar_sessao())
    canal = await stack.enter_async_context(abrir_canal())

Esse recurso combina bem com aplicações que usam rede, bancos de dados ou filas assíncronas. Veja também asyncio no Python.

Boas práticas

Use ExitStack quando a quantidade de recursos for dinâmica, quando houver recursos opcionais ou quando você precisar misturar context managers e callbacks. Para casos simples e fixos, um bloco with tradicional continua mais legível.

Registre a limpeza imediatamente após adquirir cada recurso. Isso reduz a janela em que uma exceção poderia deixar algo aberto. Evite callbacks grandes. Prefira funções pequenas e específicas. Documente quando push é usado sem __enter__, pois esse detalhe pode confundir outros desenvolvedores.

Não use a classe como substituto genérico para tratamento de erros. Ela organiza a liberação de recursos, mas não elimina a necessidade de validação, logs e mensagens claras.

Conclusão

contextlib.ExitStack oferece uma maneira elegante de gerenciar conjuntos dinâmicos de recursos. Ela mantém o comportamento seguro do with, permite registrar callbacks comuns e garante fechamento em ordem inversa. Em scripts de automação, processamento de arquivos, testes e serviços que combinam vários recursos, a classe reduz aninhamento e torna a intenção do código mais explícita.

Referências oficiais: documentação do ExitStack e PEP 343 sobre o comando with.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Desenvolvedor criando modelos de texto com string.Template no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    string.Template: templates simples e seguros

    Aprenda string.Template no Python para criar textos configuráveis, validar campos e substituir valores com segurança e clareza.

    Ler mais

    Tempo de leitura: 6 minutos
    09/09/2026
    Equipe sincronizada representando asyncio.Barrier no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    asyncio.Barrier: sincronize tarefas por etapas

    Aprenda asyncio.Barrier no Python para sincronizar tarefas em fases, evitar corridas e coordenar pipelines assíncronos com segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/09/2026
    Desenvolvedor criando modelos com dataclasses.KW_ONLY no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    dataclasses.KW_ONLY: exija argumentos nomeados

    Aprenda dataclasses.KW_ONLY no Python para criar APIs com argumentos nomeados, evitar chamadas ambíguas e evoluir modelos com segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/09/2026
    Desenvolvedor usando operator.methodcaller em código Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    operator.methodcaller: chame métodos em pipelines

    Aprenda operator.methodcaller no Python para chamar métodos em map, sorted e pipelines com argumentos e código mais declarativo.

    Ler mais

    Tempo de leitura: 5 minutos
    07/09/2026
    Estrutura de pastas percorrida com pathlib.Path.walk no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pathlib.Path.walk: percorra e filtre diretórios

    Aprenda a percorrer diretórios com pathlib.Path.walk no Python, filtrar arquivos, ignorar pastas e evitar armadilhas comuns.

    Ler mais

    Tempo de leitura: 5 minutos
    07/09/2026
    Código Python validado com enum.verify
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    enum.verify: valide regras de Enum no Python

    Aprenda enum.verify no Python para validar valores únicos, sequências contínuas e flags nomeadas com regras explícitas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/09/2026