
Passo 1 de 8
Delimitar a responsabilidade pelo recurso
Entenda o ciclo de vida que um gerenciador de contexto organiza antes de implementar seus métodos especiais.
Trilha de aprendizado · Nível 11 · Tutorial 10
Ao concluir, você poderá implementar o protocolo usado por with para adquirir e liberar recursos, incluindo caminhos de falha sem supressão acidental de exceções.
Delimitar a responsabilidade pelo recurso
Entenda o ciclo de vida que um gerenciador de contexto organiza antes de implementar seus métodos especiais. 2 min
Implementar __enter__ e escolher o valor de as
Configure o gerenciador, adquira o recurso no momento certo e escolha conscientemente o objeto que o bloco receberá após as. 3 min
Implementar a saída normal e excepcional
Faça o gerenciador liberar o recurso depois de uma entrada bem-sucedida e reconheça os dados recebidos em cada forma de saída do bloco. 4 min
Controlar a propagação sem esconder erros
Escolha o retorno de __exit__ de modo que exceções do bloco sejam preservadas por padrão e só sejam suprimidas quando isso fizer parte do contrato. 3 min
Limpar aquisições parciais quando a entrada falha
Proteja o recurso adquirido em __enter__ quando uma etapa posterior de preparação impede a entrada no bloco with. 4 min
Reconhecer falhas na própria liberação
Entenda o que acontece quando a tentativa de liberar um recurso também falha. 3 min
Testar o contrato com um recurso controlado
Execute uma suíte local com pytest para observar, de forma verificável, os caminhos de aquisição, uso, liberação e falha de um gerenciador de contexto. 3 min
Corrigir e validar um gerenciador completo
Corrija dois defeitos comuns em uma classe gerenciadora e valide, no seu computador, os caminhos de sucesso e falha. 4 min

Passo 1 de 8
Entenda o ciclo de vida que um gerenciador de contexto organiza antes de implementar seus métodos especiais.
Você já usou with com arquivos. De forma geral, ele delimita um escopo de uso: há uma entrada antes do bloco, o uso dentro dele e uma saída ao deixá-lo.
Um gerenciador de contexto é o objeto que define esse ciclo. Sua responsabilidade não é apenas fornecer algo ao bloco: ele também coordena a liberação do recurso quando aquele uso termina.
O gerenciador fica responsável pelas bordas do escopo; o código do bloco usa o recurso apenas enquanto ele está disponível.

Configurar → adquirir → usar no bloco → liberar. O bloco não assume a limpeza.
Nos próximos passos, usaremos um recurso em memória que registra eventos e expõe se foi liberado. Isso torna o ciclo de vida verificável sem depender de um arquivo, conexão ou serviço externo.
A configuração do gerenciador informa como obter o recurso. A aquisição cria ou obtém o recurso. O bloco o utiliza. Ao final, o gerenciador deve solicitar a liberação de forma explícita — não esperar que referências ao objeto desapareçam.
Exemplo
Em um uso bem-sucedido, o registro poderá mostrar uma sequência como:
["adquirido", "usado", "liberado"]E o recurso terminará com um estado equivalente a liberado = True.
A implementação dessa classe e dos métodos do protocolo virá nos próximos passos. Por enquanto, retenha a divisão de responsabilidades: o código no bloco usa; o gerenciador organiza a obtenção e a liberação.
Dica
Adote a convenção deste tutorial: crie uma nova instância do gerenciador para cada uso de with. Assim, cada instância representa um ciclo de vida claro e independente.
Coloque as etapas na ordem esperada para um uso bem-sucedido de um recurso com gerenciador de contexto.
No desenho deste tutorial, qual objeto deve assumir a responsabilidade pela liberação do recurso ao fim do escopo?

Passo 2 de 8
Configure o gerenciador, adquira o recurso no momento certo e escolha conscientemente o objeto que o bloco receberá após as.
No desenho deste tutorial, __init__ apenas guarda a configuração necessária para obter o recurso. A aquisição acontece em __enter__, quando a execução efetivamente alcança o with.
Essa separação evita adquirir um recurso ao criar um gerenciador que talvez nunca seja usado. Cada uso cria uma nova instância do gerenciador.
Observe que o nome depois de as recebe o retorno de __enter__, não necessariamente o próprio gerenciador.

__init__ configura; __enter__ adquire; o retorno de __enter__ vai para as.
A fábrica é um objeto chamável que cria o recurso. Recebê-la no construtor permite trocar a criação real por uma versão controlada em testes.
__enter__ guarda o recurso na instância gerenciadora e o devolve. Assim, o bloco usa o recurso, enquanto o gerenciador mantém a referência de que precisará na saída.
Crie uma instância nova para cada with.
class RecursoControlado:
def __init__(self, eventos):
self.eventos = eventos
self.liberado = False
class GerenciadorDeRecurso:
def __init__(self, fabrica):
self.fabrica = fabrica # configuração; ainda não adquiriu
self.recurso = None
def __enter__(self):
self.recurso = self.fabrica() # aquisição ao entrar no with
return self.recurso # valor entregue após "as"
def __exit__(self, exc_type, exc_value, traceback):
# A saída será implementada no próximo passo.
pass
eventos = []
def criar_recurso():
eventos.append("adquirido")
return RecursoControlado(eventos)
with GerenciadorDeRecurso(criar_recurso) as recurso:
print(recurso is not None) # True
print(eventos) # ['adquirido']Considere o código anterior. Relacione cada elemento à sua função ou identidade.
Toque em um item e depois no par correspondente.
Se o bloco precisar das operações do próprio gerenciador, __enter__ pode retornar self. Mas, quando o objetivo é expor somente o recurso, retornar self.recurso cria uma interface mais direta.
Em ambos os casos, a responsabilidade pela saída permanece na instância gerenciadora: with chamará seu __exit__ depois de uma entrada bem-sucedida.
Com a classe mostrada, qual afirmação é correta dentro deste bloco?
gerenciador = GerenciadorDeRecurso(criar_recurso)
with gerenciador as alvo:
...
Passo 3 de 8
Faça o gerenciador liberar o recurso depois de uma entrada bem-sucedida e reconheça os dados recebidos em cada forma de saída do bloco.
Depois que __enter__ termina com sucesso, o Python chama __exit__ ao sair do bloco with. Isso vale para o fim normal do bloco, para um return e para uma exceção que deixa o bloco.
A assinatura é __exit__(self, exc_type, exc_value, traceback). Os três últimos parâmetros descrevem uma exceção que está saindo do bloco — ou são None quando não há exceção.
O retorno do bloco e a exceção percorrem caminhos diferentes, mas ambos passam por __exit__.

Após uma entrada bem-sucedida, a saída do bloco passa por exit.
O gerenciador guarda o recurso que foi obtido em __enter__.
class SessaoControlada:
def __init__(self, fabrica):
self.fabrica = fabrica
self.recurso = None
def __enter__(self):
self.recurso = self.fabrica()
return self.recurso
def __exit__(self, exc_type, exc_value, traceback):
self.recurso.liberar()
return FalseEm uma saída sem exceção, exc_type, exc_value e traceback recebem None. Isso também ocorre quando um return dentro do with encerra a função: antes de a função entregar seu valor, o Python chama __exit__.
Exemplo
Considere que recurso.liberar() registra "liberado" em uma lista de eventos:
<code>def consultar(fabrica):
with SessaoControlada(fabrica) as recurso:
return recurso.valor
</code>
A ordem é: __enter__ obtém o recurso → o valor é preparado para retorno → __exit__(None, None, None) libera o recurso → a função entrega recurso.valor ao chamador.
O return não pula a etapa de saída.
Quando um return sai de um bloco with cuja entrada teve sucesso, exit recebe (___, ___, ___).
Se uma exceção sai do bloco, exc_type recebe sua classe, exc_value recebe a instância e traceback recebe o objeto de traceback. Ainda assim, a primeira ação básica de __exit__ é liberar o recurso.
Ao retornar False, o método informa que não deve impedir a propagação dessa exceção. A decisão de retornar False torna essa intenção visível no código.
Este exemplo registra se houve exceção, libera o recurso e preserva o fluxo original.
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is None:
self.eventos.append("saida normal")
else:
self.eventos.append(f"saida com {exc_type.__name__}")
self.recurso.liberar()
return FalseComplete o retorno para liberar o recurso sem impedir que a exceção do bloco continue: return ___.
Dica
O protocolo garante a chamada de __exit__ depois de uma entrada bem-sucedida; ele não transforma a liberação em uma operação infalível. Portanto, registre ou atualize o estado de “liberado” apenas de acordo com o contrato do próprio recurso, não apenas porque a tentativa foi feita.

Passo 4 de 8
Escolha o retorno de exit de modo que exceções do bloco sejam preservadas por padrão e só sejam suprimidas quando isso fizer parte do contrato.
Quando uma exceção sai do bloco with, Python chama __exit__ com os dados dessa exceção. O valor retornado por __exit__ decide se ela continua seu caminho:
False ou None — permite a propagação.Se não há uma regra explícita para absorver aquele erro, retorne False. Não é necessário usar raise dentro de __exit__ apenas para reenviar a exceção original.
O retorno de __exit__ só influencia uma exceção que esteja saindo do bloco.

False deixa a exceção sair; um valor verdadeiro a suprime.
A limpeza é feita em todos os casos após uma entrada bem-sucedida. O False documenta que erros do bloco não serão absorvidos.
class Sessao:
def __enter__(self):
print("adquirir")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("liberar")
return False
try:
with Sessao():
raise ValueError("dados inválidos")
except ValueError as erro:
print(f"tratada fora do with: {erro}")
# adquirir
# liberar
# tratada fora do with: dados inválidosAtenção
Evite retornar diretamente o resultado de uma operação de limpeza sem conhecer seu valor booleano. Se ela retornar um objeto verdadeiro, a exceção do bloco será suprimida por acidente.
Prefira executar a limpeza e retornar False explicitamente. A supressão deve ser uma decisão prevista e documentada pelo contrato do gerenciador.
Um gerenciador pode optar por suprimir uma exceção específica, por exemplo, se ela for esperada pelo contrato. Nesse caso, a execução continua depois do with. A instrução que lançou a exceção não é retomada nem concluída.
Este exemplo é deliberadamente específico: apenas KeyError é suprimida. Outras exceções continuam a se propagar.
class IgnorarChaveAusente:
def __enter__(self):
return {}
def __exit__(self, exc_type, exc_value, traceback):
return exc_type is KeyError
with IgnorarChaveAusente() as dados:
print("antes")
print(dados["inexistente"])
print("nunca aparece")
print("depois do with")
# antes
# depois do withDica
Em uma saída normal, os argumentos de exceção são None. Nesse cenário, o retorno de __exit__ não suprime nada e não cancela um return executado dentro do bloco with.
Qual é o resultado deste código?
class Monitor:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
print("saindo")
# retorno implícito: None
with Monitor():
raise ValueError("falhou")
print("continua")
Passo 5 de 8
Proteja o recurso adquirido em enter quando uma etapa posterior de preparação impede a entrada no bloco with.
O protocolo só considera a entrada bem-sucedida quando __enter__ termina e retorna um valor. Se ele lança uma exceção, o corpo do with não é executado e __exit__ não é chamado automaticamente.
Isso cria um caminho importante: a aquisição pode ter dado certo, mas uma preparação posterior pode falhar. Nesse caso, o próprio __enter__ deve liberar o que já adquiriu antes de deixar a exceção continuar.

A limpeza local é necessária somente se o recurso já foi adquirido, mas a entrada ainda não foi concluída.
Atenção
Não coloque a limpeza dessa aquisição parcial em __exit__: como __enter__ falhou, não há garantia de uma chamada posterior a esse método. Também não tente liberar um recurso que não chegou a ser obtido.
Depois de adquirir o recurso, envolva apenas a preparação posterior em try. Se ela falhar, libere o recurso no except e use raise sem argumento para relançar a mesma exceção ativa.
O raise simples preserva o tipo, a instância e o traceback originais da falha de preparação.
class RecursoControlado:
def __init__(self, eventos):
self.eventos = eventos
self.liberado = False
def liberar(self):
self.eventos.append("liberar")
self.liberado = True
class Sessao:
def __init__(self, fabrica, preparar):
self.fabrica = fabrica
self.preparar = preparar
self.recurso = None
def __enter__(self):
self.recurso = self.fabrica() # aquisição concluída
try:
self.preparar(self.recurso) # pode falhar
except:
self.recurso.liberar() # limpeza local
raise # relança a falha original
return self.recurso
def __exit__(self, exc_type, exc_value, traceback):
self.recurso.liberar()
return False
eventos = []
recurso = RecursoControlado(eventos)
def fabrica():
eventos.append("adquirir")
return recurso
def preparar(recurso):
eventos.append("preparar")
raise RuntimeError("preparação falhou")
try:
with Sessao(fabrica, preparar) as ativo:
eventos.append("bloco")
except RuntimeError:
eventos.append("erro observado")
print(eventos)
# ['adquirir', 'preparar', 'liberar', 'erro observado']Dica
Deixe a aquisição fora do try quando ela não exige limpeza caso falhe. Assim, a limpeza local só é executada depois que self.recurso recebeu um recurso válido.
Considere que fabrica() devolve o recurso, mas preparar(recurso) lança uma exceção. Coloque os eventos na ordem em que devem ocorrer.
Por que esse caminho não deve esperar que __exit__ libere o recurso? Explique também onde a liberação deve ocorrer.
Escreva pelo menos 80 caracteres (0/80).

Passo 6 de 8
Entenda o que acontece quando a tentativa de liberar um recurso também falha.
Depois de uma entrada bem-sucedida, o protocolo chama __exit__. Isso garante uma tentativa de liberação, não que a operação de liberação terá sucesso.
Se recurso.fechar() lançar uma exceção dentro de __exit__, essa nova exceção chega ao chamador — mesmo que o bloco with tenha terminado normalmente. Portanto, um registro como "liberação iniciada" não permite afirmar que o recurso foi liberado.
A chamada de limpeza pode concluir ou falhar; os dois caminhos precisam ser tratados como resultados diferentes.

__exit__ participa da saída, mas não torna a operação de liberação infalível.
Atenção
Evite registrar ou expor que o recurso foi “liberado” antes de fechar() retornar sem exceção. A política deste exemplo é não esconder falhas de liberação: a exceção deve permanecer visível para quem chamou o código.
Se o bloco lança uma exceção e __exit__ também lança outra durante a limpeza, a exceção da limpeza é a que chega diretamente ao chamador. A exceção original normalmente continua visível no encadeamento do traceback, como o contexto da falha posterior.
Não é necessário devolver um valor especial para produzir esse efeito: basta que a operação de liberação lance sua própria exceção.
Execute este exemplo localmente e observe o traceback completo.
class RecursoControlado:
def __init__(self, eventos, falhar_ao_liberar=False):
self.eventos = eventos
self.falhar_ao_liberar = falhar_ao_liberar
self.liberado = False
def liberar(self):
self.eventos.append("tentou liberar")
if self.falhar_ao_liberar:
raise RuntimeError("falha ao liberar")
self.liberado = True
self.eventos.append("liberou")
class Gerenciador:
def __init__(self, recurso):
self.recurso = recurso
def __enter__(self):
return self.recurso
def __exit__(self, exc_type, exc_value, traceback):
self.recurso.liberar()
return False
eventos = []
recurso = RecursoControlado(eventos, falhar_ao_liberar=True)
try:
with Gerenciador(recurso):
eventos.append("bloco დაიწყო")
raise ValueError("falha no bloco")
except RuntimeError as erro:
print(type(erro).__name__, erro)
print(eventos)
print(recurso.liberado)Exemplo
A captura recebe RuntimeError: falha ao liberar, e não o ValueError diretamente. A lista de eventos termina em ['bloco iniciou', 'tentou liberar'] se você usar esse texto no evento; no código acima, ela registra exatamente ['bloco iniciou', 'tentou liberar'] após corrigir o rótulo para português. Não aparece o evento "liberou", e recurso.liberado continua False.
No traceback, o ValueError do bloco aparece como contexto da falha de liberação.
No caminho já estudado em que __enter__ adquire o recurso e uma preparação posterior falha, a limpeza é responsabilidade do próprio __enter__. Se essa limpeza lançar uma exceção, ela também substitui a falha de preparação que estava ativa.
Assim, documente o contrato de forma precisa: o gerenciador tenta liberar recursos adquiridos; ele não promete uma liberação que a operação subjacente não conseguiu concluir.
No exemplo anterior, o bloco lança ValueError("falha no bloco") e liberar() lança RuntimeError("falha ao liberar"). Qual exceção o chamador observa diretamente? O que os eventos e o atributo liberado permitem concluir sobre a limpeza?
Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 8
Execute uma suíte local com pytest para observar, de forma verificável, os caminhos de aquisição, uso, liberação e falha de um gerenciador de contexto.
Um recurso controlado torna visível o que normalmente ocorreria fora do programa: ele registra eventos, informa se foi liberado e pode falhar em pontos escolhidos. Assim, o teste verifica o contrato, não apenas o resultado final.
Em cada cenário, observe: o valor recebido por as, a ordem dos eventos, o número de liberações e a exceção que chega ao chamador.

Depois de uma entrada bem-sucedida, a saída tenta liberar o recurso. Se __enter__ falhar, o bloco não começa e __exit__ não participa.
Associe cada cenário à verificação mais importante.
Toque em um item e depois no par correspondente.
Crie uma pasta vazia e salve o primeiro arquivo abaixo como contextos.py. A fábrica é injetada para que o teste escolha se a aquisição funciona ou falha. O recurso registra cada etapa em uma lista compartilhada.
class RecursoControlado:
def __init__(self, eventos, falhar_ao_liberar=False):
self.eventos = eventos
self.falhar_ao_liberar = falhar_ao_liberar
self.liberado = False
def liberar(self):
self.eventos.append("liberar")
if self.falhar_ao_liberar:
raise RuntimeError("falha na liberacao")
self.liberado = True
class GerenciadorRecurso:
def __init__(self, fabrica, preparar=lambda recurso: None):
self._fabrica = fabrica
self._preparar = preparar
self._recurso = None
def __enter__(self):
recurso = None
try:
recurso = self._fabrica()
self._recurso = recurso
self._preparar(recurso)
except:
if recurso is not None:
recurso.liberar()
raise
return recurso
def __exit__(self, exc_type, exc_value, traceback):
self._recurso.liberar()
return FalseSalve este segundo arquivo na mesma pasta. Ele cobre a matriz de cenários sem depender de arquivos ou serviços externos.
import pytest
from contextos import GerenciadorRecurso, RecursoControlado
def criar_fabrica(eventos, caixa, *, falhar=False, falhar_ao_liberar=False):
def fabrica():
eventos.append("adquirir")
if falhar:
raise ConnectionError("falha na aquisicao")
recurso = RecursoControlado(eventos, falhar_ao_liberar)
caixa["recurso"] = recurso
return recurso
return fabrica
def test_termino_normal_entrega_recurso_e_libera_uma_vez():
eventos, caixa = [], {}
fabrica = criar_fabrica(eventos, caixa)
with GerenciadorRecurso(fabrica) as recebido:
eventos.append("bloco")
assert recebido is caixa["recurso"]
assert eventos == ["adquirir", "bloco", "liberar"]
assert caixa["recurso"].liberado is True
def test_return_ainda_chama_a_saida():
eventos, caixa = [], {}
def usar():
with GerenciadorRecurso(criar_fabrica(eventos, caixa)):
eventos.append("bloco")
return "resultado"
assert usar() == "resultado"
assert eventos == ["adquirir", "bloco", "liberar"]
def test_excecao_do_bloco_propaga_e_recurso_e_liberado():
eventos, caixa = [], {}
with pytest.raises(ValueError, match="bloco"):
with GerenciadorRecurso(criar_fabrica(eventos, caixa)):
eventos.append("bloco")
raise ValueError("erro no bloco")
assert eventos == ["adquirir", "bloco", "liberar"]
assert caixa["recurso"].liberado is True
def test_falha_antes_da_aquisicao_nao_executa_bloco_nem_saida():
eventos, caixa = [], {}
with pytest.raises(ConnectionError, match="aquisicao"):
with GerenciadorRecurso(criar_fabrica(eventos, caixa, falhar=True)):
eventos.append("bloco")
assert eventos == ["adquirir"]
assert caixa == {}
def test_falha_de_preparacao_limpa_uma_vez_sem_entrar_no_bloco():
eventos, caixa = [], {}
def preparar(recurso):
eventos.append("preparar")
raise LookupError("falha na preparacao")
with pytest.raises(LookupError, match="preparacao"):
with GerenciadorRecurso(criar_fabrica(eventos, caixa), preparar):
eventos.append("bloco")
assert eventos == ["adquirir", "preparar", "liberar"]
assert caixa["recurso"].liberado is True
def test_falha_na_liberacao_substitui_falha_do_bloco():
eventos, caixa = [], {}
fabrica = criar_fabrica(eventos, caixa, falhar_ao_liberar=True)
with pytest.raises(RuntimeError, match="liberacao") as saida:
with GerenciadorRecurso(fabrica):
eventos.append("bloco")
raise ValueError("erro original do bloco")
assert eventos == ["adquirir", "bloco", "liberar"]
assert isinstance(saida.value.__context__, ValueError)
assert caixa["recurso"].liberado is FalseDica
No terminal aberto nessa pasta, execute:
python -m pytest -q
O resultado esperado é 6 passed. A última verificação não afirma que a liberação teve sucesso: ela confirma que a tentativa foi registrada e que a falha de liberação foi propagada.
Depois de executar a suíte, relate uma evidência de liberação e uma evidência de propagação de exceção. Inclua os nomes dos eventos ou das exceções que você observou.
Escreva pelo menos 80 caracteres (0/80).

Passo 8 de 8
Corrija dois defeitos comuns em uma classe gerenciadora e valide, no seu computador, os caminhos de sucesso e falha.
Considere esta primeira versão. Ela adquire um recurso, prepara-o e o entrega ao bloco. Porém, há dois defeitos: se prepare() falhar, o recurso já obtido não será liberado; e o retorno de release() pode ser verdadeiro, fazendo __exit__ suprimir sem querer uma exceção do bloco.
class GerenciadorRecurso:
def __init__(self, factory):
self._factory = factory
self._resource = None
def __enter__(self):
self._resource = self._factory()
self._resource.prepare()
return self._resource
def __exit__(self, exc_type, exc_value, traceback):
return self._resource.release()
A limpeza local cobre a falha antes de __enter__ retornar; __exit__ cobre somente uma entrada bem-sucedida.
A fábrica adquire o recurso. Se a preparação falhar, __enter__ libera localmente o que já adquiriu e deixa a exceção sair com raise. Depois que __enter__ retorna, __exit__ é chamado na saída do bloco: ele tenta liberar e retorna explicitamente False, preservando qualquer exceção do bloco. Se a própria liberação falhar, essa nova falha também deve aparecer ao chamador.
Crie um arquivo chamado gerenciador.py com este conteúdo.
class GerenciadorRecurso:
def __init__(self, factory):
self._factory = factory
self._resource = None
self.exit_calls = 0
def __enter__(self):
self._resource = self._factory()
try:
self._resource.prepare()
except Exception:
# __exit__ não será chamado se a entrada falhar.
self._resource.release()
raise
return self._resource
def __exit__(self, exc_type, exc_value, traceback):
self.exit_calls += 1
self._resource.release()
return FalseDica
Mesmo que release() devolva um valor verdadeiro, não o repasse como retorno de __exit__. O retorno de __exit__ controla a supressão da exceção do bloco; por isso, use False de forma explícita quando a política for propagar.
No mesmo diretório, crie test_gerenciador.py. O recurso controlado registra eventos e permite configurar falhas. Depois execute python -m pytest -q. Todos os seis testes devem passar.
import pytest
from gerenciador import GerenciadorRecurso
class RecursoControlado:
def __init__(self, events, fail_prepare=False, fail_release=False):
self.events = events
self.fail_prepare = fail_prepare
self.fail_release = fail_release
self.released = False
def prepare(self):
self.events.append("preparar")
if self.fail_prepare:
raise RuntimeError("falha ao preparar")
def release(self):
self.events.append("tentar_liberar")
if self.fail_release:
raise RuntimeError("falha ao liberar")
self.released = True
self.events.append("liberado")
return True
def fabrica(events, **falhas):
recurso = RecursoControlado(events, **falhas)
def criar():
events.append("adquirido")
return recurso
return criar, recurso
def test_entrega_o_recurso_e_libera_na_saida_normal():
events = []
criar, recurso = fabrica(events)
gerenciador = GerenciadorRecurso(criar)
with gerenciador as recebido:
assert recebido is recurso
events.append("usar")
assert recurso.released is True
assert events == ["adquirido", "preparar", "usar", "tentar_liberar", "liberado"]
def test_saida_por_return_tambem_libera():
events = []
criar, recurso = fabrica(events)
def usar():
with GerenciadorRecurso(criar) as recebido:
return recebido
assert usar() is recurso
assert recurso.released is True
def test_excecao_do_bloco_nao_e_suprimida():
events = []
criar, recurso = fabrica(events)
with pytest.raises(ValueError, match="erro no bloco"):
with GerenciadorRecurso(criar):
raise ValueError("erro no bloco")
assert recurso.released is True
def test_falha_antes_da_aquisicao_nao_chama_exit():
def criar():
raise RuntimeError("falha ao adquirir")
gerenciador = GerenciadorRecurso(criar)
with pytest.raises(RuntimeError, match="falha ao adquirir"):
with gerenciador:
pytest.fail("o bloco não deveria executar")
assert gerenciador.exit_calls == 0
def test_falha_apos_aquisicao_libera_localmente_sem_chamar_exit():
events = []
criar, recurso = fabrica(events, fail_prepare=True)
gerenciador = GerenciadorRecurso(criar)
with pytest.raises(RuntimeError, match="falha ao preparar"):
with gerenciador:
pytest.fail("o bloco não deveria executar")
assert recurso.released is True
assert gerenciador.exit_calls == 0
assert events == ["adquirido", "preparar", "tentar_liberar", "liberado"]
def test_falha_na_liberacao_substitui_a_falha_do_bloco():
events = []
criar, recurso = fabrica(events, fail_release=True)
with pytest.raises(RuntimeError, match="falha ao liberar") as capturada:
with GerenciadorRecurso(criar):
raise ValueError("erro no bloco")
assert isinstance(capturada.value.__context__, ValueError)
assert recurso.released is FalseApós executar os testes, descreva o resultado. Explique como a alteração em __enter__ evita o vazamento e como o return False evita a supressão acidental.
Escreva pelo menos 100 caracteres (0/100).
Resumo
__enter__ retornar exatamente o valor que o bloco deve usar após as; esse valor pode ser diferente do gerenciador.__enter__, libere localmente o recurso já obtido e use raise para preservar a falha ativa.__exit__ tanto na saída normal quanto na excepcional e retorne False ou None quando não quiser suprimir a exceção.release() não comprova que ela teve sucesso; documente qual falha será propagada.Justifique o contrato de falhas desta classe: quem adquire, quem libera, quando __exit__ participa e qual comportamento ocorre se a liberação falhar.
Escreva pelo menos 180 caracteres (0/180).
Parabéns! Você concluiu: Criar gerenciadores de contexto com classes
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