2.24. Gestores de contexto

A instrução with executa código de configuração, depois um corpo, depois código de limpeza – com a garantia de que a limpeza é executada mesmo quando o corpo falha a meio. O par de métodos que fornece a configuração e a limpeza chama-se gestor de contexto.

A forma é:

with <expression> as <name>:
    <body>

A expressão devolve um gestor de contexto. O Python chama o seu método __enter__, opcionalmente associa o resultado a <name>, executa o corpo e depois chama __exit__ – quer o corpo tenha terminado normalmente ou levantado uma excepção.

Flow diagram: __enter__ runs first, then the body; the body either completes normally or raises; either way, __exit__ runs at the end.

__exit__ é executado no final do bloco independentemente do que aconteça dentro dele.

2.24.1. Utilizar um gestor de contexto

O exemplo canónico é abrir um ficheiro:

with open("data.txt") as f:
    text = f.read()

# f is now closed, even if read() failed

open() devolve um objecto de ficheiro que é em si mesmo um gestor de contexto; __enter__ devolve o ficheiro e __exit__ fecha-o. O bloco with torna «fechar sempre o ficheiro quando terminar» o comportamento por omissão em vez de algo que o invocador tem de se lembrar de fazer.

2.24.1.1. Múltiplos gestores de contexto

Uma única instrução with pode entrar em vários gestores de contexto ao mesmo tempo, separados por vírgulas:

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read())

Equivalente a dois blocos with aninhados, mas mais plano. Os gestores são entrados da esquerda para a direita e saídos na ordem inversa; se __enter__ no gestor do lado direito lançar uma excepção, o __exit__ do gestor do lado esquerdo ainda é executado.

2.24.2. Escrever um gestor de contexto

Qualquer classe com os métodos __enter__ e __exit__ funciona como gestor de contexto:

class Section:
    def __init__(self, label):
        self.label = label

    def __enter__(self):
        print("---", self.label, "---")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("--- end", self.label, "---")
        return False

with Section("setup"):
    print("doing the work")

Resultado:

--- setup ---
doing the work
--- end setup ---

__exit__ recebe três argumentos que descrevem a excepção que terminou o bloco, ou três valores None se o bloco terminou normalmente. Devolver False (ou None) permite que qualquer excepção se propague após a limpeza; devolver True suprimiria a excepção.

Use gestores de contexto para qualquer recurso com um ciclo de vida «abrir / fechar» ou «adquirir / libertar» – não apenas ficheiros. O padrão mantém a limpeza emparelhada com a configuração no ponto em que ambas são introduzidas, de modo que um fecho esquecido no meio de uma função longa não pode causar fuga do recurso.