Skip to content

    Chapter 38, Intermediate

    Context managers

    The `with` statement ensures that something is undone at the end, no matter what happens. Files, connections, locks, and temporary directories use the same mechanism.

    Code for this chapter: intermediario/cap38_context_managers.py

    The protocol

    Any object with __enter__ and __exit__ works with with. The __enter__ prepares and returns the resource. The __exit__ cleans up, and receives the exception data, if any:

    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)
    
    Output
    True
    

    The value that the __exit__ returns decides the fate of the exception: False lets it continue, True the suppresses. The __exit__ runs even when the block fails:

    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")
    
    Output
    saindo, erro: ZeroDivisionError
    continuou
    

    The short form: contextmanager

    Writing a class just for that is overkill. The decorator contextlib.contextmanager transforms a generator with a single yield into a context manager. Everything before the yield is the __enter__. Everything after is the __exit__. Put the cleanup in a 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")
    
    Output
    [início] importação
    trabalhando
    [fim] importação
    

    Ready-made tools

    The standard library already includes the most common cases. The suppress explicitly ignores a specific exception, and the TemporaryDirectory creates a folder that disappears at the end:

    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())
    
    Output
    sem erro
    True
    False
    

    Exercise 1

    Change directory and return

    Write mudar_pasta(destino), a context manager that changes the working directory and always restores it at the end, even if the block fails.