Capítulo 38, Intermediário
Gerenciadores de contexto
O `with` garante que algo seja desfeito no fim, aconteça o que acontecer. Arquivos, conexões, travas e diretórios temporários usam o mesmo mecanismo.
Código deste capítulo: intermediario/cap38_context_managers.py
O protocolo
Qualquer objeto com __enter__ e __exit__ funciona com with. O __enter__ prepara e devolve o recurso. O __exit__ limpa, e recebe os dados da exceção, se houve:
import time
class Cronometro:
def __enter__(self):
self.inicio = time.perf_counter()
return self
def __exit__(self, tipo_exc, valor_exc, traceback):
self.duracao = time.perf_counter() - self.inicio
return False
with Cronometro() as cron:
sum(range(100_000))
print(cron.duracao >= 0)
True
O valor que o __exit__ devolve decide o destino da exceção: False deixa ela seguir, True a suprime. O __exit__ roda mesmo quando o bloco falha:
class Ignorar:
def __enter__(self):
return self
def __exit__(self, tipo_exc, valor_exc, traceback):
print("saindo, erro:", tipo_exc.__name__ if tipo_exc else None)
return tipo_exc is ZeroDivisionError
with Ignorar():
1 / 0
print("continuou")
saindo, erro: ZeroDivisionError
continuou
A forma curta: contextmanager
Escrever uma classe só para isso é cerimônia. O decorador contextlib.contextmanager transforma um gerador com um único yield em um gerenciador de contexto. Tudo antes do yield é o __enter__. Tudo depois é o __exit__. Coloque a limpeza em um finally:
from contextlib import contextmanager
@contextmanager
def secao(titulo):
print(f"[início] {titulo}")
try:
yield
finally:
print(f"[fim] {titulo}")
with secao("importação"):
print("trabalhando")
[início] importação
trabalhando
[fim] importação
Ferramentas prontas
A biblioteca padrão já traz os casos mais comuns. O suppress ignora uma exceção específica de forma explícita, e o TemporaryDirectory cria uma pasta que desaparece no fim:
from contextlib import suppress
from pathlib import Path
import tempfile
with suppress(FileNotFoundError):
Path("nao_existe.txt").unlink()
print("sem erro")
with tempfile.TemporaryDirectory() as pasta:
arquivo = Path(pasta) / "tmp.txt"
arquivo.write_text("oi", encoding="utf-8")
print(arquivo.exists())
print(Path(pasta).exists())
sem erro
True
False
Exercício 1
Mudar de pasta e voltar
Escreva mudar_pasta(destino), um gerenciador de contexto que mude a pasta de trabalho e sempre a restaure no fim, mesmo se o bloco falhar.