Gerenciadores de Contexto como um Padrão
Gerenciadores de contexto garantem a configuração e a limpeza em torno de um bloco de código. A instrução with chama __enter__/__exit__ (ou @contextmanager yield) para que os recursos sejam liberados mesmo quando exceções ocorrem - arquivos são fechados, locks são liberados e transações são desfeitas.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
from contextlib import contextmanager
@contextmanager
def temporary_setting(obj, attr, value):
old = getattr(obj, attr)
setattr(obj, attr, value)
try:
yield obj
finally:
setattr(obj, attr, old)
with temporary_setting(config, "debug", True):
run_checks()Quando usar isso:
- Pares de aquisição/liberação - arquivos, sockets, locks, conexões de banco de dados
- Estado temporário - variáveis de ambiente, níveis de log, flags de recursos para um bloco
- Transações - commit em caso de sucesso, rollback em caso de exceção
- Tempo e profiling -
enterinicia o timer,exitregistra a duração - Supressão de erros esperados -
contextlib.suppress(FileNotFoundError)
Exemplo de Trabalho
Um gerenciador baseado em classe para transações de banco de dados mais um @contextmanager para medição de tempo.
import time
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Iterator
@dataclass
class FakeConnection:
committed: bool = False
rolled_back: bool = False
def commit(self) -> None:
self.committed = True
def rollback(self) -> None:
self.rolled_back = True
class Transaction:
def __init__(self, conn: FakeConnection) -> None:
self._conn = conn
def __enter__(self) -> FakeConnection:
return self._conn
def __exit__(self, exc_type, exc, tb) -> bool:
if exc_type is None:
self._conn.commit()
else:
self._conn.rollback()
return False # não suprimir exceções
@contextmanager
def timer(label: str) -> Iterator[None]:
start = time.perf_counter()
try:
yield
finally:
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed:.3f}s")
conn = FakeConnection()
with timer("caminho feliz"):
with Transaction(conn) as db:
print("escreve linhas", db)
print("commitado:", conn.committed)
conn2 = FakeConnection()
try:
with Transaction(conn2):
raise ValueError("boom")
except ValueError:
pass
print("desfeito:", conn2.rolled_back)O que isso demonstra:
- Protocolo de classe:
__enter__retorna o recurso,__exit__limpa - Retornar
Falsede__exit__propaga exceções após o rollback @contextmanagerenvolve um gerador: código antes doyieldé a entrada, depois é a saídafinallyé executado quer o bloco saia normalmente ou por exceção
Mergulho Profundo
Como Funciona
with expr as var:avaliaexpr, chama__enter__, vinculavar, executa o bloco- Na saída,
__exit__(exc_type, exc_val, tb)é sempre executado - Se
__exit__retornar um valor verdadeiro, a exceção é suprimida @contextmanagerenvolve um gerador: código antes doyieldé a entrada, depois é a saídacontextlib.ExitStackcompõe múltiplos gerenciadores sem aninhamento profundo
Tipos Comuns de Gerenciadores
| Padrão | Entrada | Saída |
|---|---|---|
| E/S de Arquivo | abre o handle | fecha o handle |
| Lock | adquire | libera |
| Transação | inicia | commit ou rollback |
| Diretório temporário | cria o caminho | deleta a árvore |
Notas Python
from contextlib import ExitStack, suppress
with ExitStack() as stack:
f1 = stack.enter_context(open("a.txt"))
f2 = stack.enter_context(open("b.txt"))
# ambos fecham na saída
with suppress(FileNotFoundError):
open("optional.cfg").read()Armadilhas
- Esquecer
finallyem geradores - a limpeza deve estar emfinallyem torno doyield, não apenas depois dele em caso de sucesso. Correção: sempre envolva o código pós-yieldemtry/finally. - Suprimir exceções acidentalmente -
__exit__retornandoTrueesconde bugs. Correção: retorneFalsea menos que a supressão seja intencional (documente-a). - Locks reentrantes - nem todos os gerenciadores são reentrantes; aninhar o mesmo lock incorretamente causa deadlock. Correção: use
threading.RLockou documente a não reentrância. - Gerar apenas uma vez -
@contextmanagerdeve gerar exatamente um valor. Correção: não gere em um loop. - Vazamento de recurso em falha de
__enter__- se a configuração for parcialmente bem-sucedida e depois falhar, a saída pode não ser executada. Correção: useExitStackou mantenha a configuração mínima em__enter__.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
try/finally manual | Uso único, sem necessidade de reutilização | O padrão se repete em toda a base de código |
Handlers atexit | Limpeza ao final do processo | Limpeza por requisição ou por escopo |
weakref.finalize | Limpeza de objetos acionada pelo GC | Requer tempo determinístico |
| Decorators | Envolver funções inteiras | Blocos inline arbitrários precisam de with |
FAQs
Qual a diferença entre um gerenciador de classe e @contextmanager?
Classes implementam __enter__/__exit__. @contextmanager transforma um gerador com um único yield no mesmo protocolo - menos código repetitivo para casos simples.
O __exit__ pode suprimir exceções?
Sim, retornando True. Use com moderação - os chamadores perdem a visibilidade das falhas, a menos que esse seja o contrato (suppress).
O 'with' funciona com código assíncrono?
Use async with e __aenter__/__aexit__, ou @asynccontextmanager. with síncrono não pode usar await.
O que 'as var' vincula?
O valor de retorno de __enter__ (ou o valor gerado por @contextmanager). Muitos gerenciadores retornam self ou o recurso subjacente.
Como compor muitos gerenciadores?
contextlib.ExitStack entra em cada gerenciador e desfaz na ordem inversa na saída.
open() é um gerenciador de contexto?
Sim. with open(path) as f fecha o arquivo mesmo que a leitura gere um erro.
Posso usar gerenciadores de contexto em __post_init__ de dataclass?
Prefira que os chamadores usem with em torno do escopo de construção. Objetos de longa duração devem expor .close() ou usar finalizadores weakref.
E sobre contextlib.closing?
Envolve objetos com um método .close() em um gerenciador de contexto quando eles não têm suporte nativo.
Transações devem sempre usar gerenciadores de contexto?
Sim, para código de aplicação - o par commit/rollback é propenso a erros sem um __exit__ garantido.
Como testar gerenciadores de contexto?
Afirme o estado do recurso antes, durante e após o with. Acione exceções dentro do bloco para verificar se a limpeza ainda é executada.
Relacionado
- EAFP vs LBYL - exceções dentro de blocos gerenciados
- Padrões Factory e Builder - construção de recursos
- Injeção de Dependência em Python - injeção de conexões
- Noções Básicas de Padrões Pythonicos - visão geral da seção
Versões de Stack: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.