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.







