Módulo 6 - Context managers e o with
O protocolo __enter__ e __exit__
10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026
O que você vai aprender
- Entender que with chama __enter__ ao entrar e __exit__ ao sair.
- Escrever uma classe context manager com os dois métodos.
- Saber que o retorno de __enter__ é o que vai para o as.
- Reconhecer os parâmetros de __exit__ e seu papel.
Ouvir o resumo desta aula
Um recap de cerca de 2 minutos na voz do Valim, para ouvir no trânsito ou na academia.
Ler a transcrição do resumo
Resumo da aula: O protocolo __enter__ e __exit__.
Os objetivos desta aula. Entender que with chama __enter__ ao entrar e __exit__ ao sair. Escrever uma classe context manager com os dois métodos. Saber que o retorno de __enter__ é o que vai para o as. Reconhecer os parâmetros de __exit__ e seu papel.
Veja o essencial, parte por parte.
Os dois métodos que definem um context manager. Um context manager é um objeto com os métodos __enter__ e __exit__.
Os parâmetros do __exit__. O __enter__ é simples; o __exit__ tem um detalhe importante.
Montando o seu, do começo ao fim. __init__: recebe e guarda a configuração (ainda não adquire o recurso).
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
Os dois métodos que definem um context manager
O with não é mágico: ele apenas chama dois métodos especiais do objeto que você passa. Ao entrar no bloco, o Python chama o __enter__ do objeto; o valor que esse método devolve é atribuído à variável depois do as. Ao sair do bloco, por qualquer motivo, o Python chama o __exit__, onde vai a limpeza. Qualquer objeto que tenha esses dois métodos pode ser usado com with. Criar um context manager é, no fundo, escrever essas duas funções.
class Conexao:
def __init__(self, host):
self.host = host
def __enter__(self):
print(f"conectando a {self.host}")
self.ativa = True
return self # vai para a variavel do as
def __exit__(self, exc_tipo, exc_valor, traceback):
print(f"desconectando de {self.host}")
self.ativa = False
return False # nao suprime excecoes
with Conexao("servidor") as conn:
print(f"usando conexao, ativa = {conn.ativa}")
# conectando a servidor
# usando conexao, ativa = True
# desconectando de servidorUma classe com __enter__ e __exit__ vira um context manager utilizável com with.
Acompanhe a ordem. O with Conexao("servidor") as conn cria o objeto e chama seu __enter__, que imprime a conexão e devolve self; por isso conn passa a ser a própria conexão. O bloco roda. Na saída, o Python chama __exit__, que imprime a desconexão. Repare que __enter__ costuma devolver self quando o objeto útil é ele mesmo, mas poderia devolver qualquer coisa, um arquivo, um cursor, um valor. É esse retorno que aparece depois do as.
Os parâmetros do __exit__
O __enter__ é simples; o __exit__ tem um detalhe importante. Ele recebe três parâmetros que descrevem a exceção que porventura ocorreu dentro do bloco: o tipo da exceção, o valor (a instância do erro) e o traceback. Se o bloco terminou sem erro, os três chegam como None. Esse desenho permite que o __exit__ saiba se a saída foi tranquila ou por falha, e reaja de formas diferentes, por exemplo desfazendo uma transação em caso de erro e confirmando em caso de sucesso.
| Parâmetro de __exit__ | O que contém | Se não houve erro |
|---|---|---|
| exc_tipo | A classe da exceção (ex.: ValueError) | None |
| exc_valor | A instância da exceção | None |
| traceback | O objeto de rastreamento | None |
Os três parâmetros de __exit__ descrevem a exceção; None significa saída sem erro.
class Transacao:
def __enter__(self):
print("iniciando transacao")
return self
def __exit__(self, exc_tipo, exc_valor, traceback):
if exc_tipo is None:
print("commit: tudo certo")
else:
print(f"rollback: erro {exc_tipo.__name__}")
return False
with Transacao():
print("gravando dados")
raise ValueError("dado invalido")
# iniciando transacao
# gravando dados
# rollback: erro ValueError
# ... e a excecao continua subindoO __exit__ inspeciona exc_tipo para decidir entre confirmar e desfazer a operação.
Montando o seu, do começo ao fim
Junte as peças e você tem uma receita: no __init__ guarde a configuração; no __enter__ adquira o recurso e devolva o que o usuário vai manipular; no __exit__ libere o recurso e decida o que fazer com uma eventual exceção. Essa estrutura funciona para arquivos, conexões, bloqueios, medições de tempo, mudanças temporárias de configuração e muito mais. Sempre que você pensar em algo que tem começo e fim garantidos, um context manager de classe é a ferramenta.
Vale separar bem o __init__ do __enter__. O __init__ apenas prepara os dados; a aquisição de verdade acontece no __enter__, quando o with começa. Essa separação permite, por exemplo, criar o objeto uma vez e usá-lo em vários blocos with. Nas próximas aulas você vai ver que, para casos simples, existe um atalho que evita escrever a classe inteira. Mas entender o protocolo de classe é a base: o atalho é apenas uma forma mais curta de produzir os mesmos __enter__ e __exit__.
Teste rápido
No protocolo de context manager, o que vai para a variável depois do as em with obj as x?
Perguntas frequentes
- O __enter__ precisa devolver self?
- Não precisa. É comum devolver self quando o próprio objeto é o recurso útil, mas o __enter__ pode devolver qualquer coisa: um arquivo aberto, um cursor de banco, um valor calculado. O que ele devolve é o que aparece depois do as. Se você não precisa de nenhum valor no bloco, pode nem usar o as.
- O que são os três parâmetros do __exit__?
- São informações sobre a exceção que porventura ocorreu no bloco: o tipo da exceção, a instância dela e o traceback. Se o bloco terminou sem erro, os três chegam como None. Com esses dados, o __exit__ decide como agir, por exemplo confirmar a operação quando não houve erro e desfazê-la quando houve.
- Quando o __exit__ é chamado?
- Sempre que o fluxo sai do bloco with, aconteça o que acontecer: saída normal ao chegar ao fim do bloco, um return, um break, ou uma exceção. Essa garantia é justamente o que torna o with confiável para limpeza. Mesmo que uma exceção esteja subindo, o __exit__ roda antes de o erro continuar.
- Devo adquirir o recurso no __init__ ou no __enter__?
- No __enter__. O __init__ deve só guardar a configuração; a aquisição de fato (abrir o arquivo, conectar, travar o lock) pertence ao __enter__, que roda quando o with começa. Assim o objeto pode ser criado antes e o recurso só é adquirido no momento certo, e você pode até reutilizar o objeto em vários blocos.
- Preciso criar uma classe para todo context manager?
- Não. A classe com __enter__ e __exit__ é a forma mais completa e é útil quando há estado a guardar. Para casos simples, o contextlib.contextmanager permite criar um gerenciador a partir de uma função com yield, que você vê na próxima aula. Os dois produzem o mesmo comportamento; muda só a quantidade de código.
Fontes
Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.