Módulo 6 - Context managers e o with

O atalho: @contextlib.contextmanager

10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Usar @contextlib.contextmanager para criar um gerenciador com uma função.
  • Entender que o código antes do yield é a entrada e o depois é a saída.
  • Colocar a limpeza em um finally para cobrir exceções.
  • Escolher entre a classe e a função com yield conforme o caso.

Um context manager em uma função

A classe com __enter__ e __exit__ é poderosa, mas exagerada para casos simples. O módulo contextlib traz um atalho elegante: o decorador contextmanager. Você escreve uma função geradora, aquelas com yield, e o decorador a transforma em um context manager. A leitura fica natural: tudo que vem antes do yield é a preparação, equivalente ao __enter__; o valor que você cede com yield é o que vai para o as; e tudo que vem depois do yield é a limpeza, equivalente ao __exit__.

from contextlib import contextmanager

@contextmanager
def conexao(host):
    print(f"conectando a {host}")   # entrada (__enter__)
    conn = {"host": host, "ativa": True}
    try:
        yield conn                   # o valor vai para o as
    finally:
        conn["ativa"] = False
        print(f"desconectando de {host}")  # saida (__exit__)

with conexao("servidor") as c:
    print(f"usando, ativa = {c['ativa']}")
# conectando a servidor
# usando, ativa = True
# desconectando de servidor

Antes do yield é a entrada; depois do yield, dentro do finally, é a limpeza.

Compare com a classe Conexao da aula anterior: o comportamento é idêntico, mas o código cabe em menos linhas e se lê de cima para baixo como uma pequena história. Por isso, na prática do dia a dia, essa é a forma mais comum de criar context managers próprios. A classe fica reservada para quando você precisa guardar bastante estado ou reaproveitar o objeto em contextos diferentes.

Por que a limpeza vai no finally

Há um detalhe que faz toda a diferença: envolver o yield em um try com finally. Quando ocorre uma exceção dentro do bloco with, o Python a repassa para dentro da função geradora, no ponto do yield, como se ela tivesse acontecido ali. Se a limpeza estivesse solta depois do yield, sem o finally, essa exceção pularia a limpeza, exatamente o problema que o with deveria resolver. Colocar a limpeza no finally garante que ela rode aconteça o que acontecer, fiel à promessa do with.

from contextlib import contextmanager

@contextmanager
def recurso():
    print("adquire")
    try:
        yield
    finally:
        print("libera")  # roda mesmo se o bloco lancar excecao

try:
    with recurso():
        print("usando")
        raise RuntimeError("falhou")
except RuntimeError:
    print("erro tratado fora")
# adquire
# usando
# libera        <- a limpeza rodou apesar do erro
# erro tratado fora

O finally garante a limpeza mesmo quando o bloco with lança uma exceção.

Classe ou função: quando usar cada uma

Com as duas formas na mão, surge a pergunta: qual usar? A resposta é sobre estado e reaproveitamento. Se o gerenciador é simples, faz uma preparação e uma limpeza e pronto, a função com yield é mais curta e clara, e deve ser a primeira escolha. Se o gerenciador guarda bastante estado, tem métodos próprios ou precisa ser reutilizado como objeto em vários lugares, a classe com __enter__ e __exit__ organiza melhor. As duas produzem context managers equivalentes; muda a ergonomia.

Função com @contextmanager

  • Ideal para casos simples e diretos
  • Menos código, leitura de cima para baixo
  • Preparação e limpeza numa função só
  • A forma mais comum no dia a dia

Classe com __enter__/__exit__

  • Ideal quando há bastante estado a guardar
  • Permite métodos próprios no objeto
  • Reutilizável como objeto em vários blocos
  • Mais verbosa, porém mais estruturada

Uma boa heurística: comece pela função com yield e só migre para a classe se sentir falta de estrutura. O contextlib ainda traz utilitários que combinam com essa abordagem, como o suppress e o ExitStack, que a próxima aula apresenta. O importante é que, tendo entendido o protocolo __enter__ e __exit__ e o atalho com yield, você já consegue criar gerenciadores de contexto para praticamente qualquer recurso com começo e fim.

Teste rápido

Em um context manager criado com @contextmanager, o que representa o código depois do yield?

Perguntas frequentes

Por que a função precisa de exatamente um yield?
Porque o contextmanager usa o yield como a fronteira entre entrada e saída: uma pausa única onde o bloco with roda. Zero yields não define essa fronteira, e mais de um faria o gerador ceder controle duas vezes, o que o decorador rejeita com um erro. A estrutura é sempre: preparar, um yield, limpar.
O que acontece com a exceção do bloco dentro da função geradora?
Ela é injetada no ponto do yield, como se tivesse ocorrido ali. Por isso, se você quiser reagir ao erro, envolve o yield com try, except; e para garantir a limpeza sempre, usa o finally. Se você não capturar, a exceção continua subindo normalmente depois que a limpeza rodar.
Preciso sempre ceder um valor com o yield?
Não. Se o bloco with não precisa de nenhum objeto, você pode escrever só yield, sem valor, e usar with recurso(): sem o as. O gerenciador ainda faz a preparação e a limpeza; ele só não entrega nada para o bloco usar. É comum em gerenciadores que mudam um estado global temporariamente.
A função com yield vira um gerador de verdade?
Sim, por baixo dos panos ela é uma função geradora, e o decorador contextmanager a envolve para expor os métodos __enter__ e __exit__. Você não interage com ela como um gerador comum, não itera sobre ela; o decorador cuida de avançar até o yield na entrada e de retomá-la na saída.
Quando prefiro a classe em vez da função com yield?
Quando o gerenciador tem bastante estado, métodos próprios ou precisa ser reutilizado como objeto em contextos diferentes. Para a maioria dos casos simples, a função com yield é mais curta e legível. Uma boa regra é começar pela função e só migrar para a classe se sentir falta de estrutura.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.