2.24. Gerenciadores de contexto

A instrução with executa um código de configuração, depois um corpo e, então, um código de finalização – com a garantia de que a finalização seja executada mesmo quando o corpo falha no meio do caminho. O par de métodos que fornece a configuração e a finalização é chamado de gerenciador de contexto.

O formato é:

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

A expressão retorna um gerenciador de contexto. O Python chama seu método __enter__, opcionalmente vincula o resultado a <name>, executa o corpo e, em seguida, chama __exit__ – quer o corpo tenha sido concluído normalmente, quer tenha lançado uma exceção.

Diagrama de fluxo: __enter__ é executado primeiro, depois o corpo; o corpo é concluído normalmente ou lança uma exceção; de qualquer forma, __exit__ é executado no final.

__exit__ é executado ao final do bloco, aconteça o que acontecer dentro dele.

2.24.1. Usando um gerenciador de contexto

O exemplo canônico é abrir um arquivo:

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

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

open() retorna um objeto de arquivo que é ele próprio um gerenciador de contexto; __enter__ retorna o arquivo, e __exit__ o fecha. O bloco with torna “sempre fechar o arquivo quando terminar” o comportamento padrão, em vez de algo que o chamador precise lembrar.

2.24.1.1. Múltiplos gerenciadores de contexto

Uma única instrução with pode entrar em vários gerenciadores de contexto de uma só vez, 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 gerenciadores são acionados da esquerda para a direita e encerrados em ordem inversa; se o __enter__ do gerenciador à direita lançar uma exceção, o __exit__ do gerenciador à esquerda ainda é executado.

2.24.2. Escrevendo um gerenciador de contexto

Qualquer classe com métodos __enter__ e __exit__ funciona como um gerenciador 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")

Saída:

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

__exit__ recebe três argumentos descrevendo a exceção que encerrou o bloco, ou três valores None se o bloco foi concluído normalmente. Retornar False (ou None) permite que qualquer exceção se propague após a finalização; retornar True a suprimiria.

Use gerenciadores de contexto para qualquer recurso que tenha um ciclo de vida do tipo “abrir / fechar” ou “adquirir / liberar” – não apenas arquivos. O padrão mantém a limpeza emparelhada com a configuração no ponto em que ambas são introduzidas, de modo que um fechamento esquecido no meio de uma função longa não possa vazar o recurso.