Módulo 6 - Context managers e o with
Casos reais: arquivos, locks, transações e cronômetro
10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026
O que você vai aprender
- Reconhecer arquivos e locks como context managers prontos.
- Entender por que with é essencial para liberar um lock sempre.
- Montar um gerenciador de transação com commit e rollback.
- Criar um cronômetro de bloco com @contextmanager.
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: Casos reais: arquivos, locks, transações e cronômetro.
Os objetivos desta aula. Reconhecer arquivos e locks como context managers prontos. Entender por que with é essencial para liberar um lock sempre. Montar um gerenciador de transação com commit e rollback. Criar um cronômetro de bloco com @contextmanager.
Veja o essencial, parte por parte.
Arquivos e locks já vêm prontos. Objetos de arquivo e locks de threading já são context managers.
Transação: confirmar ou desfazer. O commit vem depois do yield, no caminho de sucesso.
Um cronômetro de bloco. Para fechar, um gerenciador simples e muito útil: um cronômetro que mede quanto tempo um bloco levou.
Esse foi o resumo do essencial. Para se aprofundar, leia a aula completa e responda os exercícios.
Arquivos e locks já vêm prontos
Dois recursos essenciais já chegam prontos para o with. O primeiro é o arquivo, que você abre com with open e não precisa fechar à mão. O segundo é o lock de threads, usado quando várias threads podem mexer no mesmo dado e você precisa garantir que só uma faça isso por vez. O lock precisa ser liberado sempre, e é exatamente aí que o with brilha: se o trecho protegido lançar uma exceção, o lock ainda é liberado, evitando que o programa inteiro trave à espera de um bloqueio que ninguém solta.
import threading
contador = 0
trava = threading.Lock()
def incrementar():
global contador
with trava: # adquire o lock ao entrar
atual = contador
contador = atual + 1
# o lock e liberado ao sair, mesmo se algo falhar dentro
# Sem o with, uma excecao no trecho protegido deixaria a trava
# fechada para sempre, travando as demais threads.with trava garante que o lock seja liberado, mesmo se o trecho protegido falhar.
Repare que você não chama trava.acquire nem trava.release à mão. O with cuida dos dois: adquire ao entrar e libera ao sair. Esse é o motivo de o with ser a forma recomendada de usar locks. Liberar um lock à mão é a receita para o dia em que uma exceção pula o release e o programa congela, um bug angustiante de diagnosticar. A garantia de limpeza deixa de ser conveniência e vira requisito de correção.
Transação: confirmar ou desfazer
Um caso onde o context manager mostra todo o seu valor é a transação. A regra é: se o bloco terminou sem erro, confirme as alterações (commit); se houve uma exceção, desfaça tudo (rollback) para não deixar o banco em estado inconsistente. Essa lógica se encaixa perfeitamente no __exit__, que sabe se houve exceção pelos seus parâmetros. O gerenciador abaixo simula um banco para ilustrar; a estrutura é a mesma que bibliotecas reais adotam.
from contextlib import contextmanager
class Banco:
def __init__(self):
self.pendentes = []
def gravar(self, item):
self.pendentes.append(item)
def commit(self):
print(f"commit: {self.pendentes}")
self.pendentes = []
def rollback(self):
print(f"rollback: descartando {self.pendentes}")
self.pendentes = []
@contextmanager
def transacao(banco):
try:
yield banco
banco.commit() # sem erro: confirma
except Exception:
banco.rollback() # com erro: desfaz
raise # e deixa o erro subir
banco = Banco()
try:
with transacao(banco) as b:
b.gravar("linha 1")
raise ValueError("deu ruim")
except ValueError:
print("erro tratado")
# rollback: descartando ['linha 1'] / erro tratadocommit quando o bloco termina bem; rollback e re-raise quando ocorre uma exceção.
Um cronômetro de bloco
Para fechar, um gerenciador simples e muito útil: um cronômetro que mede quanto tempo um bloco levou. Ele encaixa no molde do @contextmanager como uma luva. Antes do yield, você marca o instante inicial; depois do yield, calcula e mostra a duração. Como o pós-yield fica no finally, a medição acontece mesmo que o bloco lance uma exceção, o que é ótimo para descobrir se uma operação que falhou também estava lenta. É o mesmo espírito do decorador de cronômetro do Módulo 5, agora aplicado a um trecho arbitrário de código.
from contextlib import contextmanager
import time
@contextmanager
def cronometro(rotulo):
inicio = time.perf_counter()
try:
yield
finally:
duracao = time.perf_counter() - inicio
print(f"{rotulo}: {duracao:.4f}s")
with cronometro("processamento"):
total = sum(i * i for i in range(1_000_000))
# processamento: 0.0xxx sCronômetro de bloco: mede o tempo do trecho, mesmo que ele lance exceção.
Com esses casos, o quadro fica completo. Arquivos e locks vêm prontos; transações e cronômetros você monta com o protocolo e o atalho do yield. O fio comum é sempre o mesmo: um recurso ou uma ação tem começo e fim, e o with garante que o fim aconteça, aconteça o que acontecer no meio. Levar esse hábito para o seu código, usar with com tudo que o suporta e criar gerenciadores para os seus próprios recursos, é uma marca de código robusto e profissional.
Teste rápido
Por que usar with em um lock de threads é considerado essencial, e não só conveniente?
Perguntas frequentes
- Por que não liberar o lock à mão com acquire e release?
- Porque é frágil. Se uma exceção acontecer entre o acquire e o release, a linha do release não roda e o lock fica preso, travando todas as threads que dependem dele. O with adquire e libera automaticamente, garantindo a liberação mesmo com erro. Por isso a forma recomendada de usar um lock é sempre with trava.
- No gerenciador de transação, por que dar raise depois do rollback?
- Porque desfazer as alterações não faz o problema desaparecer. Quem chamou a transação precisa saber que ela falhou para tratar a situação, avisar o usuário, tentar de novo, registrar o erro. O rollback cuida da consistência do banco; o raise cuida de propagar a falha. Esconder a exceção depois do rollback criaria bugs silenciosos.
- O cronômetro mede o tempo mesmo se o bloco der erro?
- Sim, porque a medição fica no finally, depois do yield. O finally roda na saída do bloco aconteça o que acontecer, inclusive quando uma exceção está subindo. Isso é útil justamente para descobrir se uma operação que falhou também estava demorando, um sinal que ajuda no diagnóstico.
- Bibliotecas de banco reais usam esse padrão de transação?
- Usam, sim. Muitas conexões de banco de dados oferecem suporte a with para justamente confirmar ou desfazer a transação conforme o bloco termine bem ou com erro. O exemplo do curso simula um banco para deixar a lógica visível, mas a estrutura, commit no sucesso e rollback no erro, é a mesma que você encontra nas bibliotecas de produção.
- Posso combinar vários desses context managers de uma vez?
- Pode. Você aninha ou separa por vírgula quando o número é fixo, e usa o ExitStack quando é variável. Por exemplo, abrir um arquivo dentro de uma transação, tudo cronometrado. Como cada gerenciador garante a própria limpeza, combiná-los mantém a robustez: cada recurso é liberado na saída, na ordem correta.
Fontes
Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.