
Passo 1 de 8
Do gerador ao gerenciador de contexto
Use @contextmanager para transformar um gerador em um gerenciador utilizável com with e acompanhar suas três fases de execução.
Trilha de aprendizado · Nível 11 · Tutorial 11
Ao concluir, você poderá expressar ciclos simples de aquisição e liberação com contextlib.contextmanager, usando um gerador sem comprometer a propagação de falhas.
Do gerador ao gerenciador de contexto
Use @contextmanager para transformar um gerador em um gerenciador utilizável com with e acompanhar suas três fases de execução. 2 min
Respeitar o contrato de um único yield
Analise os caminhos de execução de um gerenciador criado com @contextmanager e garanta que cada entrada bem-sucedida produza exatamente um valor. 2 min
Acompanhar a exceção até o yield
Veja os dois caminhos de saída de um contexto baseado em gerador e acompanhe o destino de uma exceção levantada dentro do bloco with. 3 min
Proteger a liberação com finally
Use try/finally ao redor do yield para liberar um recurso simulado tanto após o uso normal quanto quando o bloco with falha. 3 min
Observar falhas sem escondê-las
Registre a falha que ocorreu dentro do bloco with sem impedir que ela continue para o código externo. 3 min
Criar um gerenciador para cada uso
Cada chamada a uma função decorada com @contextmanager cria um gerenciador destinado a um único ciclo de entrada e saída. 2 min
Escolher entre gerador e classe
Escolha a forma de implementação pela clareza do ciclo de vida e da interface necessária. 2 min
Aplicação final: um contexto que libera e propaga
Prática final para validar aquisição, liberação e propagação de falhas em um gerenciador baseado em gerador. 5 min

Passo 1 de 8
Use @contextmanager para transformar um gerador em um gerenciador utilizável com with e acompanhar suas três fases de execução.
Você já implementou o protocolo diretamente com __enter__ e __exit__. Com contextlib.contextmanager, uma função geradora pode expressar o mesmo ciclo de forma linear:
<ul><li>o código antes de yield prepara o contexto;</li><li>o valor do yield vai para o nome após as;</li><li>o código depois de yield é retomado quando o bloco with termina normalmente.</li></ul>
O decorador adapta essa função geradora para que ela possa ser usada por with.

O yield é a fronteira entre o gerenciador e o código consumidor.
Este exemplo só registra eventos em uma lista. Ainda não há um recurso real para liberar.
from contextlib import contextmanager
@contextmanager
def registrar(eventos):
eventos.append("preparar")
yield "sessao-42"
eventos.append("retomar")
eventos = []
contexto = registrar(eventos)
eventos.append("gerenciador criado")
with contexto as sessao:
eventos.append(f"consumir {sessao}")
print(eventos)
# ['gerenciador criado', 'preparar', 'consumir sessao-42', 'retomar']registrar(eventos) cria um gerenciador; por isso, "gerenciador criado" aparece antes de "preparar".
Na entrada do with, o gerador avança até yield "sessao-42". O nome sessao recebe a string "sessao-42", e não o objeto gerenciador. Ao fim normal do bloco, o gerador é retomado e registra "retomar".
Dica
Pense no yield como uma porta: o gerenciador chega até ela, entrega um valor ao consumidor e fica suspenso enquanto o bloco with executa.
Relacione cada elemento do código à fase que ele representa.
Toque em um item e depois no par correspondente.

Passo 2 de 8
Analise os caminhos de execução de um gerenciador criado com @contextmanager e garanta que cada entrada bem-sucedida produza exatamente um valor.
Uma função decorada com @contextmanager deve executar yield exatamente uma vez em cada entrada que for bem-sucedida.
Não conte apenas quantos yield aparecem no código: acompanhe o caminho realmente percorrido. Na entrada, o gerador precisa chegar ao primeiro yield. Na saída do with, ele pode terminar, mas não pode produzir outro valor.
O mesmo yield separa a entrada da saída do contexto.

Entrada: chegar a um yield. Saída: retomar e encerrar sem outro yield.
Exemplo
from contextlib import contextmanager
@contextmanager
def acesso(ativo):
if not ativo:
return
yield "recurso"
with acesso(False) as valor:
print(valor)Com ativo=False, a função termina normalmente antes do primeiro yield. Como a entrada não recebeu valor algum, contextlib sinaliza RuntimeError.
Exemplo
from contextlib import contextmanager
@contextmanager
def acesso():
yield "primeiro"
yield "segundo"
with acesso() as valor:
print(valor)O primeiro valor é associado a valor. Ao sair do with, o gerador é retomado e tenta produzir "segundo". Isso também gera RuntimeError: um gerenciador não pode fornecer um segundo valor nessa etapa.
Atenção
Um return antes do primeiro yield encerra o gerador normalmente e viola o contrato, resultando em RuntimeError. Já um raise ValueError(...) na preparação é uma falha explícita: essa exceção é propagada, e o bloco with não chega a começar.
Analise o número de valores produzidos em cada chamada.
from contextlib import contextmanager
@contextmanager
def lote(itens):
for item in itens:
yield item
with lote(["A", "B"]) as item:
print(item)O laço não torna vários recursos disponíveis dentro do mesmo with. Ele produz "A" na entrada e tenta produzir "B" na saída. Portanto, esse uso viola o contrato e gera RuntimeError.
Para ser válido, cada caminho de uma entrada bem-sucedida deve alcançar um único yield, e a retomada posterior deve encerrar o gerador.
Considere:
@contextmanager
def conexao(modo):
if modo == "fechado":
return
if modo == "indisponivel":
raise ConnectionError("serviço indisponível")
yield "conectado"O que ocorre em with conexao("fechado") as estado:?
Qual implementação respeita o contrato para qualquer valor de ativo?

Passo 3 de 8
Veja os dois caminhos de saída de um contexto baseado em gerador e acompanhe o destino de uma exceção levantada dentro do bloco with.
Depois de entregar um valor com yield, o gerador fica suspenso enquanto o bloco with é executado. Se o bloco termina normalmente, o gerador é retomado e segue para a próxima instrução.
Se o bloco levanta uma exceção, o adaptador criado por @contextmanager a reintroduz exatamente no ponto em que o gerador estava suspenso: o yield. Sem código que trate essa exceção, ela interrompe o gerador e continua para fora do with.
Compare o retorno normal ao gerador com o caminho de uma falha no bloco consumidor.

Na saída excepcional, a exceção chega ao yield; por isso, uma instrução comum logo depois dele não é alcançada.
Exemplo
Com este gerenciador, a saída normal alcança a instrução após o yield:
<code>eventos = []
with observar(eventos) as valor:
eventos.append(f"consumir {valor}")
print(eventos)
Execute este script no seu computador e observe a lista impressa.
from contextlib import contextmanager
@contextmanager
def observar(eventos):
eventos.append("preparar")
yield "conexão"
eventos.append("retomar normalmente")
eventos = []
try:
with observar(eventos) as valor:
eventos.append(f"consumir {valor}")
raise ValueError("falha do consumidor")
except ValueError as erro:
eventos.append(f"fora do with: {erro}")
print(eventos)
# ['preparar', 'consumir conexão', 'fora do with: falha do consumidor']Dica
A mensagem "retomar normalmente" não aparece porque a exceção interrompe o gerador no próprio yield. O except externo apenas torna visível que a mesma falha continuou sua propagação para fora do with.
Considere o script anterior. Coloque os eventos na ordem em que ocorrem quando o bloco with levanta ValueError.

Passo 4 de 8
Use try/finally ao redor do yield para liberar um recurso simulado tanto após o uso normal quanto quando o bloco with falha.
Em um gerenciador criado com @contextmanager, organize um ciclo linear:
Depois de uma entrada bem-sucedida, o gerador fica suspenso no yield enquanto o corpo do with executa. Ao sair do bloco — normalmente ou por exceção — ele é retomado e o finally executa a liberação.
O finally é alcançado nos dois caminhos de saída que vêm depois de uma aquisição bem-sucedida.

Após o yield, a retomada normal e a retomada por exceção passam pela mesma etapa de liberação.
Dica
Coloque o try imediatamente depois de a aquisição terminar com sucesso. Assim, o finally protege exatamente o período em que o recurso existe e precisa ser liberado.
Crie um arquivo chamado contexto_finally.py, cole o código completo abaixo e execute python contexto_finally.py. O recurso é apenas um objeto em memória: os eventos permitem observar a ordem das operações.
Um gerenciador baseado em gerador com limpeza garantida após a aquisição.
from contextlib import contextmanager
class RecursoSimulado:
def __init__(self, eventos):
self.eventos = eventos
self.aberto = False
def adquirir(self):
self.aberto = True
self.eventos.append("adquirir")
def liberar(self):
self.aberto = False
self.eventos.append("liberar")
@contextmanager
def usar_recurso(eventos):
recurso = RecursoSimulado(eventos)
recurso.adquirir()
try:
yield recurso
finally:
recurso.liberar()
# Saída normal
eventos_normais = []
with usar_recurso(eventos_normais) as recurso:
assert recurso.aberto is True
eventos_normais.append("usar")
assert eventos_normais == ["adquirir", "usar", "liberar"]
assert recurso.aberto is False
print("normal:", eventos_normais)
# Saída por exceção
eventos_com_falha = []
try:
with usar_recurso(eventos_com_falha) as recurso:
assert recurso.aberto is True
eventos_com_falha.append("usar")
raise ValueError("falha do consumidor")
except ValueError as erro:
eventos_com_falha.append(f"fora: {erro}")
assert eventos_com_falha == [
"adquirir",
"usar",
"liberar",
"fora: falha do consumidor",
]
assert recurso.aberto is False
print("falha:", eventos_com_falha)Exemplo
normal: ['adquirir', 'usar', 'liberar']
falha: ['adquirir', 'usar', 'liberar', 'fora: falha do consumidor']
Na segunda execução, liberar ocorre antes de o except externo observar o ValueError. O gerenciador não captura a exceção: ela atravessa o yield, aciona o finally e continua para fora do with.
O finally protege o que vem depois de uma aquisição bem-sucedida. Se recurso.adquirir() lançar uma exceção antes de o fluxo alcançar o try e o yield, o bloco with não chegou a ser aberto e não existe uma saída posterior dele.
Por isso, uma aquisição que pode falhar parcialmente deve cuidar da própria limpeza parcial no ponto em que ela acontece. Não presuma que o finally ao redor do yield resolverá algo que não foi adquirido com sucesso.
Após executar o script, descreva as duas sequências de eventos e explique por que elas demonstram, ao mesmo tempo, liberação garantida e propagação da exceção.
Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 8
Registre a falha que ocorreu dentro do bloco with sem impedir que ela continue para o código externo.
Quando o bloco with falha, a exceção reaparece no yield. Portanto, um except que envolve o yield pode observá-la.
Mas há uma consequência importante: se esse except captura a exceção e o gerador termina normalmente, a exceção é suprimida. O código após o with continua como se a falha tivesse sido tratada.
Para apenas registrar a ocorrência e preservar a falha, use raise sem argumento dentro do except. Ele relança a mesma exceção ativa.

Terminar o except suprime a exceção capturada; raise sem argumento a mantém em propagação.
O except registra o problema, raise o relança e o finally libera o recurso nos dois caminhos de saída.
from contextlib import contextmanager
@contextmanager
def usar_recurso(eventos):
recurso = {"ativo": True}
eventos.append("adquirir")
try:
yield recurso
except Exception as erro:
eventos.append(f"falha: {erro}")
raise
finally:
recurso["ativo"] = False
eventos.append("liberar")
eventos = []
try:
with usar_recurso(eventos) as recurso:
assert recurso["ativo"]
raise ValueError("dados inválidos")
except ValueError as erro:
eventos.append(f"externo: {erro}")
print(eventos)
# ['adquirir', 'falha: dados inválidos', 'liberar', 'externo: dados inválidos']Atenção
Na implementação com classe, __exit__ retorna um valor booleano para indicar supressão ou propagação. Em uma função decorada com @contextmanager, não use return False esperando esse efeito.
Se o except que recebeu a exceção terminar normalmente — inclusive com return False — a exceção capturada será suprimida. Para propagá-la após registrá-la, a instrução necessária é raise sem argumento.
Exemplo
except Exception as erro:
eventos.append(f"falha: {erro}")
# fim do except: a exceção não chega ao código externoO finally ainda executa a liberação, mas o chamador não poderá capturar a falha original porque ela foi suprimida.
Complete a frase: depois de registrar uma exceção capturada no except, use ____ sem argumento para que ela continue para fora do with.

Passo 6 de 8
Cada chamada a uma função decorada com @contextmanager cria um gerenciador destinado a um único ciclo de entrada e saída.
A chamada de uma função decorada com @contextmanager cria um gerenciador ligado a um único ciclo: entrar no with, suspender no yield e encerrar na saída. Depois desse ciclo, essa mesma instância não deve entrar em outro with.
Para um novo uso, chame a função decorada novamente. A função é uma fábrica de gerenciadores; o valor que ela retornou não é reutilizável.
Compare uma instância já encerrada com novas instâncias criadas por chamadas separadas.

Cada chamada cria seu próprio percurso até o encerramento.
Dica
Se a segunda entrada falhar, identifique primeiro a causa estrutural: a mesma instância já completou seu ciclo. Não baseie a correção no nome exato da exceção ou na mensagem exibida pela sua versão do Python.
A variável sessao guarda um único gerenciador. O segundo with tenta reiniciar algo que já terminou.
from contextlib import contextmanager
@contextmanager
def abrir_sessao():
print("abrir")
try:
yield "conexão"
finally:
print("fechar")
sessao = abrir_sessao()
with sessao as conexao:
print(conexao)
with sessao as conexao: # uso inválido da mesma instância
print(conexao)Agora cada with recebe uma instância nova.
from contextlib import contextmanager
@contextmanager
def abrir_sessao():
print("abrir")
try:
yield "conexão"
finally:
print("fechar")
with abrir_sessao() as conexao:
print(conexao)
with abrir_sessao() as conexao:
print(conexao)Nos dois exemplos, o valor após as continua sendo "conexão", produzido pelo yield. A diferença está no objeto que controla o ciclo: no código correto, cada chamada de abrir_sessao() cria outro gerenciador.
Em contextos aninhados, crie uma instância para cada entrada.
from contextlib import contextmanager
@contextmanager
def marcador(nome):
print(f"entra: {nome}")
try:
yield nome
finally:
print(f"sai: {nome}")
with marcador("externo") as fora:
with marcador("interno") as dentro:
print(f"{fora} -> {dentro}")No exemplo aninhado, as duas entradas são válidas porque marcador("externo") e marcador("interno") criam gerenciadores diferentes.

Passo 7 de 8
Escolha a forma de implementação pela clareza do ciclo de vida e da interface necessária.
Use @contextmanager quando o recurso segue um roteiro local e direto: preparar, entregar um valor ao with e liberar. O código antes do yield prepara; o bloco consumidor usa o valor; finally finaliza.
A escolha não é uma competição por menos linhas. Pergunte: o contexto precisa apenas conduzir esse ciclo ou precisa oferecer uma interface própria?

Um ciclo localizado favorece o gerador; estado e operações além da entrada e saída favorecem uma classe.
Prefira uma classe quando o gerenciador precisa manter estado persistente ou expor operações públicas que façam parte do uso do recurso. Por exemplo, se o código consumidor precisa consultar métricas, alterar uma configuração ou chamar uma operação específica durante o contexto, uma interface de objeto pode deixar essas responsabilidades explícitas.
Isso não torna a classe automaticamente reutilizável ou reentrante. Essas propriedades dependem do ciclo de vida que a implementação realmente define.
Exemplo
Ciclo linear — gerador
@contextmanager
def registrar(eventos):
eventos.append("início")
try:
yield eventos
finally:
eventos.append("fim")O valor é entregue e depois liberado; não há outra operação pública necessária.
Interface própria — classe
Um recurso que precisa expor, durante o with, operações como pausar(), retomar() e uma propriedade total_processado tem uma interface maior que o simples ciclo de entrada e saída. Uma classe tende a tornar esse contrato mais claro.
Relacione cada requisito à implementação mais adequada.
Toque em um item e depois no par correspondente.

Passo 8 de 8
Prática final para validar aquisição, liberação e propagação de falhas em um gerenciador baseado em gerador.
Nesta aplicação, o gerenciador deve adquirir um recurso simulado, entregar seu valor após as, registrar uma falha do bloco consumidor, relançá-la e liberar o recurso em qualquer saída. Cada chamada a sessao(...) cria um gerenciador novo para um uso.

Depois do yield, a retomada pode ser normal ou excepcional. O finally une os dois caminhos na liberação.
Dica
Confira estes quatro pontos no código: um único yield; except envolvendo o yield; raise sem argumento após registrar a falha; e finally para a liberação.
No seu computador, crie um arquivo chamado validar_contexto.py, copie o código completo abaixo e execute python validar_contexto.py. Os asserts verificam o valor de as, a ordem dos eventos, a propagação da exceção e dois usos com novas instâncias.
from contextlib import contextmanager
@contextmanager
def sessao(eventos):
eventos.append("adquirir")
recurso = {"id": "R1"}
try:
yield recurso
except Exception as erro:
eventos.append(f"falha:{type(erro).__name__}")
raise
finally:
eventos.append("liberar")
# Caso 1: saída normal e valor entregue por "as".
eventos = []
with sessao(eventos) as recurso:
assert recurso == {"id": "R1"}
eventos.append("consumidor:sucesso")
assert eventos == [
"adquirir",
"consumidor:sucesso",
"liberar",
]
# Caso 2: a falha do consumidor é registrada, liberada e continua observável fora.
eventos = []
try:
with sessao(eventos) as recurso:
assert recurso["id"] == "R1"
eventos.append("consumidor:falha")
raise ValueError("dados inválidos")
except ValueError as erro:
assert str(erro) == "dados inválidos"
eventos.append("fora:ValueError")
assert eventos == [
"adquirir",
"consumidor:falha",
"falha:ValueError",
"liberar",
"fora:ValueError",
]
# Caso 3: cada chamada cria um novo gerenciador para um novo uso.
eventos = []
with sessao(eventos) as primeiro:
eventos.append(f"uso:{primeiro['id']}:1")
with sessao(eventos) as segundo:
eventos.append(f"uso:{segundo['id']}:2")
assert primeiro is not segundo
assert eventos == [
"adquirir",
"uso:R1:1",
"liberar",
"adquirir",
"uso:R1:2",
"liberar",
]
print("Todas as verificações passaram.")Após executar o script, relate a sequência observada no caso de falha. Explique por que o ValueError continuou observável fora do with e como os dois usos evitaram reutilizar o mesmo gerenciador.
Escreva pelo menos 120 caracteres (0/120).
Resumo
Use @contextmanager quando preparação, entrega e limpeza formarem um ciclo linear e localizado. Prefira uma classe quando o recurso exigir estado persistente ou operações públicas além da entrada e saída do contexto.
Parabéns! Você concluiu: Criar gerenciadores de contexto com contextlib
Você concluiu este nível!
Agora você vai iniciar: Tipagem e contratos de interface
Verificar anotações com mypyExecutar o mypy em módulos anotados, interpretar seus diagnósticos e corrigir incompatibilidades sem desativar a verificação com Any.Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks