Pular para o conteúdo

    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:

    intermediario/cap38_context_managers.pylinhas 10 a 25
    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)
    
    Saída
    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:

    intermediario/cap38_context_managers.pylinhas 27 a 38
    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")
    
    Saída
    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:

    intermediario/cap38_context_managers.pylinhas 43 a 56
    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")
    
    Saída
    [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:

    intermediario/cap38_context_managers.pylinhas 61 a 73
    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())
    
    Saída
    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.