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

    Vivid close-up of code on a computer screen showcasing programming details.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ast no Python: analise código-fonte

    Aprenda ast no Python para analisar e transformar código, criar visitors, preservar posições, usar literal_eval e evitar riscos de execução.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Rustic exposed brick wall featuring aged electrical sockets and metal conduit.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    socket no Python: redes TCP e UDP

    Aprenda socket no Python para clientes e servidores TCP, UDP, framing, timeouts, IPv6, concorrência, TLS e segurança de rede.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    multiprocessing no Python: vários núcleos

    Aprenda multiprocessing no Python com processos, pools, filas, pipes, memória compartilhada, cancelamento, segurança e shutdown correto.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    Monochrome image showcasing concentric circles in a tunnel-like structure creating a sense of depth.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    concurrent.futures: threads e processos em paralelo

    Aprenda concurrent.futures no Python com threads, processos, Future, timeouts, cancelamento, backpressure e prevenção de deadlocks.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    Striking image of a red-bellied python showcasing its vibrant scales in dramatic lighting.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    winsound no Python: sons no Windows

    Aprenda winsound no Python para tocar WAV, sons do sistema, beeps, loops e notificações assíncronas com segurança no Windows.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    winreg no Python: Registro do Windows

    Aprenda winreg no Python para ler e gravar o Registro do Windows, controlar permissões, tipos, WOW64, exclusões e segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    26/08/2026