contextlib no Python: gerencie recursos

Publicado em: 27/08/2026
Tempo de leitura: 7 minutos
Young professional woman working on a laptop in an office setting, concentrating on her task.

O módulo contextlib fornece ferramentas para criar e combinar context managers. Eles controlam a entrada e a saída de um bloco with, garantindo cleanup de arquivos, sockets, locks, transações, diretórios temporários e outros recursos mesmo quando ocorre uma exceção. O módulo reduz classes repetitivas e ajuda a tornar lifecycle e tratamento de erros explícitos.

Um context manager não serve apenas para fechar objetos. Ele pode configurar estado temporário, registrar métricas, redirecionar streams, iniciar e encerrar transações ou compor uma quantidade dinâmica de recursos. O ponto central é que toda aquisição tenha uma liberação correspondente e previsível.

O protocolo de contexto

Um context manager implementa __enter__() e __exit__(). O valor retornado por __enter__() é associado ao alvo de as. __exit__() recebe informações sobre a exceção, quando houver.

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

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

Retornar um valor verdadeiro de __exit__() suprime a exceção. Faça isso apenas quando o erro tiver sido realmente tratado.

contextmanager

O decorator @contextmanager transforma uma função generator em context manager. O código antes de yield executa na entrada; o código em finally, na saída.

from contextlib import contextmanager

@contextmanager
def conexao_temporaria():
    conexao = abrir_conexao()
    try:
        yield conexao
    finally:
        conexao.close()

with conexao_temporaria() as conexao:
    conexao.executar()

A função deve produzir exatamente um valor. Se não chegar ao yield ou produzir mais de uma vez, o protocolo falha.

Coloque cleanup em finally

Sem finally, uma exceção lançada pelo bloco pode pular a liberação.

@contextmanager
def arquivo_bloqueado(caminho):
    arquivo = open(caminho, "a+")
    adquirir_lock(arquivo)
    try:
        yield arquivo
    finally:
        liberar_lock(arquivo)
        arquivo.close()

Se a aquisição puder falhar parcialmente, libere apenas o que foi realmente adquirido.

Exceções dentro do generator

Quando o bloco with lança uma exceção, ela é reinjetada no ponto do yield. O generator pode registrar, converter ou tratar o erro.

@contextmanager
def registrar_falhas(logger):
    try:
        yield
    except Exception:
        logger.exception("falha no bloco")
        raise

Reexecute raise quando a exceção não foi resolvida. Apenas registrar e continuar pode esconder corrupção de estado.

ContextDecorator

Context managers baseados em ContextDecorator também podem decorar funções.

from contextlib import ContextDecorator

class Cronometrar(ContextDecorator):
    def __enter__(self):
        self.inicio = agora()
        return self

    def __exit__(self, *exc):
        registrar_duracao(agora() - self.inicio)
        return False

@Cronometrar()
def processar():
    executar_tarefa()

O objeto precisa suportar uso repetido quando o decorator puder chamar a função várias vezes.

closing

closing(objeto) chama close() ao sair. É útil para objetos legados que possuem fechamento, mas não implementam o protocolo de contexto.

from contextlib import closing

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

Não envolva objetos que já suportam with sem necessidade. O context manager nativo pode executar etapas adicionais além de close().

aclosing

aclosing() é a versão assíncrona para objetos com aclose(), especialmente generators assíncronos.

from contextlib import aclosing

async with aclosing(stream_assincrono()) as stream:
    async for item in stream:
        if item.pronto:
            break

O cleanup ocorre no mesmo contexto assíncrono, preservando context variables, exceções e lifecycle da tarefa.

asynccontextmanager

@asynccontextmanager cria context managers assíncronos a partir de async generators.

from contextlib import asynccontextmanager

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

Use async with. O cleanup pode aguardar operações, mas ainda precisa de timeout e cancelamento adequado.

Context managers reutilizáveis

Alguns managers são single-use; outros podem ser reutilizados; alguns são reentrantes. Essas propriedades não são equivalentes.

Um generator decorado cria uma nova instância a cada chamada da função, portanto use with recurso(): e não armazene a mesma instância para reutilização.

nullcontext

nullcontext() não faz cleanup e apenas retorna um valor opcional. Ele simplifica caminhos em que o recurso pode já estar aberto.

from contextlib import nullcontext

contexto = open(caminho) if caminho else nullcontext(stream_existente)
with contexto as stream:
    processar(stream)

Evite duplicar o corpo do with em dois branches.

suppress

suppress(*excecoes) ignora exceções específicas.

from contextlib import suppress

with suppress(FileNotFoundError):
    caminho.unlink()

Use somente quando a exceção representa um resultado esperado e não há ação necessária. Não suprima Exception de forma ampla.

Suprimir não é registrar

Se o erro precisa de auditoria, retry ou métrica, um try/except explícito é mais claro. suppress comunica que a ausência do efeito é aceitável e silenciosa.

redirect_stdout

redirect_stdout(destino) troca temporariamente sys.stdout.

from contextlib import redirect_stdout
from io import StringIO

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

A alteração é global ao processo e afeta outras threads. Use principalmente em scripts, testes controlados e ferramentas single-thread.

redirect_stderr

redirect_stderr() faz o mesmo para sys.stderr. Ele não captura escrita feita diretamente em descritores nativos, subprocessos independentes ou logging configurado para outro destino.

Para subprocessos, use os parâmetros de captura de subprocess.

chdir temporário

chdir(caminho) altera o diretório atual durante o bloco e restaura o anterior na saída.

from contextlib import chdir

with chdir("projeto"):
    executar_build()

O diretório atual é estado global do processo. Não use esse padrão em programas com várias threads ou tarefas concorrentes que dependem de paths relativos.

ExitStack

ExitStack compõe uma quantidade dinâmica de context managers e callbacks de cleanup.

from contextlib import ExitStack

with ExitStack() as stack:
    arquivos = [
        stack.enter_context(open(caminho, encoding="utf-8"))
        for caminho in caminhos
    ]
    combinar(arquivos)

Se a abertura do terceiro arquivo falhar, os anteriores são fechados automaticamente.

Ordem LIFO

Callbacks do ExitStack executam em ordem inversa à aquisição. Isso combina com recursos dependentes: o recurso mais recente é liberado primeiro.

Registre cleanup imediatamente depois de adquirir cada recurso para não deixar uma janela de vazamento.

callback

stack.callback(funcao, *args, **kwargs) registra uma chamada sem receber informações de exceção.

with ExitStack() as stack:
    diretorio = criar_diretorio_temporario()
    stack.callback(remover_arvore, diretorio)
    executar(diretorio)

Callbacks devem ser idempotentes quando possível, pois cleanup pode enfrentar estado parcial.

push

push() registra a parte de saída de um context manager ou função compatível com __exit__. Diferente de callback, ela pode receber e suprimir exceções.

Use com cuidado: suprimir uma exceção altera o que callbacks externos enxergam.

enter_context

enter_context(cm) chama __enter__() e registra __exit__(). O valor retornado é o mesmo que seria associado por as.

Essa operação torna simples construir um conjunto de recursos definido em runtime.

pop_all

pop_all() transfere os callbacks para outra stack sem executá-los. Isso ajuda em aquisição “tudo ou nada”.

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

Depois da transferência, o novo proprietário precisa fechar a pilha.

AsyncExitStack

AsyncExitStack combina context managers síncronos, assíncronos e callbacks de cleanup assíncronos.

from contextlib import AsyncExitStack

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

É especialmente útil quando a quantidade de conexões é dinâmica.

push_async_callback

Callbacks assíncronos podem aguardar liberação, flush ou encerramento. Eles também executam em ordem inversa.

Planeje timeouts; um cleanup assíncrono travado pode impedir o shutdown da aplicação.

Transações

Um context manager de transação pode fazer commit quando o bloco termina normalmente e rollback quando ocorre exceção.

@contextmanager
def transacao(conexao):
    try:
        yield conexao
    except Exception:
        conexao.rollback()
        raise
    else:
        conexao.commit()

Não suprima a exceção depois do rollback sem comunicar a falha ao caller.

Aquisição parcial

Se a preparação possui várias etapas, use ExitStack internamente para registrar cada cleanup. Só transfira a stack após todas as etapas terem sucesso.

Esse padrão evita flags booleanas e blocos finally complexos.

Exceções no cleanup

Uma exceção durante cleanup pode substituir ou encadear a exceção original. Use raise ... from ..., logging e tipos específicos para preservar diagnóstico.

Não ignore falha ao fazer commit, flush ou fechar um recurso que garante durabilidade.

Vários managers em um with

Para quantidade fixa, um único with com vários managers é mais simples.

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

Use ExitStack quando a quantidade ou os tipos forem dinâmicos.

Context managers e sockets

Sockets já implementam o protocolo de contexto e são fechados ao sair.

import socket

with socket.create_connection((host, porta), timeout=5) as sock:
    sock.sendall(dados)

Veja socket no Python para timeouts, framing e shutdown.

Context managers e e-mail

Clientes smtplib.SMTP também podem ser usados com with, garantindo encerramento da sessão. Consulte smtplib no Python.

Locks

Locks de threading e multiprocessing funcionam como context managers. O bloco reduz o risco de esquecer a liberação.

with lock:
    atualizar_estado()

Ainda é necessário evitar deadlock, manter a seção curta e adquirir locks em ordem consistente.

Context variables

Context managers podem definir uma ContextVar temporariamente e restaurar o token na saída.

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

Isso é útil em logging e tracing, inclusive em código assíncrono.

Métricas

Um manager pode medir duração e resultado do bloco. Registre sucesso ou falha na saída, mas preserve a exceção.

Evite que falhas no sistema de métricas escondam o erro principal da aplicação.

Tipagem

Use typing.ContextManager, AsyncContextManager ou tipos estruturais adequados para declarar APIs. Anote o tipo produzido pelo yield.

Uma assinatura clara evita confundir o objeto manager com o recurso retornado.

Testes

Teste saída normal, exceção no bloco, exceção durante aquisição, cleanup falhando, uso repetido, cancelamento assíncrono e aquisição parcial. Verifique ordem de liberação.

Mocks devem confirmar que close, rollback e callbacks acontecem exatamente quando esperado.

Erros comuns

Os erros mais frequentes são esquecer finally, produzir mais de uma vez em @contextmanager, suprimir exceções acidentalmente, usar redirect_stdout em programa multithread, alterar diretório global concorrentemente, reutilizar manager single-use, registrar cleanup tarde demais e esquecer de fechar uma stack transferida.

Conclusão

contextlib torna aquisição e liberação de recursos mais seguras e legíveis. Use @contextmanager para managers simples, ExitStack para recursos dinâmicos, versões assíncronas para cleanup aguardável e nullcontext para caminhos opcionais.

Não esconda erros importantes e mantenha ownership explícito. Consulte a documentação oficial de contextlib e a referência do protocolo de context manager.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Rack de servidores representando balanceamento de conexões com SO_REUSEPORT_LB no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    SO_REUSEPORT_LB: distribua conexões entre workers

    Aprenda SO_REUSEPORT_LB no Python para distribuir conexões entre múltiplos workers com segurança, testes e portabilidade.

    Ler mais

    Tempo de leitura: 6 minutos
    11/10/2026
    Código Python assíncrono em notebook para inspect.markcoroutinefunction
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    markcoroutinefunction: identifique wrappers async

    Aprenda inspect.markcoroutinefunction no Python para identificar wrappers assíncronos, integrar frameworks e evitar detecção incorreta de corrotinas.

    Ler mais

    Tempo de leitura: 6 minutos
    10/10/2026
    Código Python para percorrer pastas e arquivos com Path.walk
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Path.walk: percorra diretórios com segurança

    Aprenda Path.walk no Python para percorrer diretórios, filtrar arquivos, tratar erros e controlar a travessia com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    10/10/2026
    Depuração de processo Python em terminal com código
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pdb -p: depure processos Python em execução

    Aprenda a anexar o pdb a um processo Python em execução, inspecionar pilhas e diagnosticar travamentos com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    09/10/2026
    Código Python e representação de frações numéricas
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    fractions.from_number: converta números em frações

    Aprenda fractions.from_number no Python para converter números em frações exatas, controlar precisão e evitar arredondamentos inesperados.

    Ler mais

    Tempo de leitura: 5 minutos
    09/10/2026
    Desenvolvedor configurando servidor HTTPS e certificado TLS com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crie servidor HTTPS local no Python

    Aprenda HTTPSServer no Python para servir HTTPS localmente, configurar certificados, usar threads e entender limites de segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026