Context Managers em Python: Dominando o with
Aprenda context managers em Python com exemplos práticos usando classes e contextlib. Domine o with, crie seus próprios gerenciadores e use async with.
Esquecer de fechar um arquivo, não liberar uma conexão com o banco de dados, deixar um lock travado — esses são erros sutis que causam bugs difíceis de rastrear. Os context managers do Python resolvem isso com elegância, garantindo que recursos sejam sempre liberados, mesmo quando ocorrem exceções.
O Que São Context Managers
Um context manager é qualquer objeto que implementa os métodos __enter__ e __exit__. Quando usado com a instrução with, o Python garante que __enter__ é chamado no início do bloco e __exit__ é chamado ao final — sempre, mesmo se uma exceção ocorrer no meio.
O exemplo mais comum é a abertura de arquivos:
# Sem context manager — risco de vazamento de recurso
f = open("dados.txt", "r")
try:
conteudo = f.read()
finally:
f.close()
# Com context manager — limpo e seguro
with open("dados.txt", "r") as f:
conteudo = f.read()
# f.close() é chamado automaticamente aqui
A versão com with é mais curta, mais legível e mais segura. Para mais detalhes sobre manipulação de arquivos, veja nosso guia de manipulação de arquivos em Python.
Criando Context Managers com Classes
Para criar seu próprio context manager, implemente __enter__ e __exit__:
class Temporizador:
"""Mede o tempo de execução de um bloco de código."""
def __enter__(self):
import time
self.inicio = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.duracao = time.perf_counter() - self.inicio
print(f"Executado em {self.duracao:.4f}s")
return False # não suprime exceções
# Uso
with Temporizador() as t:
soma = sum(range(1_000_000))
print(f"Duração capturada: {t.duracao:.4f}s")
O método __exit__ recebe três argumentos sobre exceções:
exc_type— a classe da exceção (ouNone)exc_val— a instância da exceção (ouNone)exc_tb— o traceback (ouNone)
Se __exit__ retornar True, a exceção é suprimida. Se retornar False (ou None), a exceção é propagada normalmente. Na maioria dos casos, você quer propagar exceções — retorne False. Para entender melhor o tratamento de exceções, confira nosso guia de tratamento de erros em Python.
O Decorador @contextmanager
Criar uma classe inteira só para um context manager simples pode ser excessivo. O módulo contextlib oferece o decorador @contextmanager, que permite criar context managers usando geradores:
from contextlib import contextmanager
@contextmanager
def diretorio_temporario(caminho: str):
"""Muda para um diretório e volta ao original no final."""
import os
original = os.getcwd()
try:
os.makedirs(caminho, exist_ok=True)
os.chdir(caminho)
yield caminho
finally:
os.chdir(original)
# Uso
with diretorio_temporario("/tmp/meu_projeto") as dir:
print(f"Trabalhando em: {dir}")
# ... operações no diretório temporário
# Automaticamente volta ao diretório original
A estrutura é sempre a mesma:
- Antes do
yield— código de setup (equivalente ao__enter__) - O
yield— o valor entregue aoasdowith - Depois do
yield(nofinally) — código de cleanup (equivalente ao__exit__)
Esse padrão se conecta com geradores e iteradores, já que @contextmanager usa um gerador por baixo dos panos.
Context Managers para Banco de Dados
Um dos usos mais práticos é gerenciar conexões e transações de banco de dados:
from contextlib import contextmanager
import sqlite3
@contextmanager
def conexao_db(caminho: str):
"""Gerencia conexão SQLite com commit/rollback automático."""
conn = sqlite3.connect(caminho)
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
@contextmanager
def transacao(conn):
"""Gerencia uma transação individual."""
cursor = conn.cursor()
try:
yield cursor
conn.commit()
except Exception:
conn.rollback()
raise
finally:
cursor.close()
# Uso
with conexao_db("minha_app.db") as conn:
with transacao(conn) as cursor:
cursor.execute("CREATE TABLE IF NOT EXISTS usuarios (id INTEGER PRIMARY KEY, nome TEXT)")
cursor.execute("INSERT INTO usuarios (nome) VALUES (?)", ("Ana",))
Esse padrão é usado extensivamente por ORMs como SQLAlchemy e por drivers de PostgreSQL e SQLite. Também é comum em conexões com Redis e outros serviços.
Context Managers Aninhados e ExitStack
Quando você precisa gerenciar múltiplos recursos, pode aninhar with statements ou usar uma única linha:
# Múltiplos context managers em uma linha
with open("entrada.txt") as entrada, open("saida.txt", "w") as saida:
saida.write(entrada.read().upper())
Para um número dinâmico de context managers, use ExitStack:
from contextlib import ExitStack
def processar_arquivos(caminhos: list[str]):
with ExitStack() as stack:
arquivos = [
stack.enter_context(open(caminho))
for caminho in caminhos
]
for arquivo in arquivos:
print(arquivo.readline())
# Todos os arquivos são fechados automaticamente ao sair do with
processar_arquivos(["config.txt", "dados.csv", "log.txt"])
O ExitStack é especialmente útil quando a quantidade de recursos é determinada em tempo de execução.
Utilitários Úteis do contextlib
O módulo contextlib oferece vários context managers prontos:
suppress — ignorar exceções específicas
from contextlib import suppress
import os
# Em vez de try/except para ignorar FileNotFoundError
with suppress(FileNotFoundError):
os.remove("arquivo_temporario.txt")
redirect_stdout — redirecionar saída
from contextlib import redirect_stdout
from io import StringIO
buffer = StringIO()
with redirect_stdout(buffer):
print("Essa saída vai para o buffer")
resultado = buffer.getvalue()
print(f"Capturado: {resultado}")
closing — garantir chamada de close()
from contextlib import closing
from urllib.request import urlopen
with closing(urlopen("https://python.dev.br")) as pagina:
conteudo = pagina.read()
Context Managers Assíncronos
Com o crescimento de programação assíncrona em Python, context managers assíncronos se tornaram essenciais. Eles usam __aenter__ e __aexit__ com async with:
from contextlib import asynccontextmanager
import aiohttp
@asynccontextmanager
async def sessao_http():
"""Gerencia uma sessão HTTP assíncrona."""
session = aiohttp.ClientSession()
try:
yield session
finally:
await session.close()
# Uso
async def buscar_dados():
async with sessao_http() as session:
async with session.get("https://api.exemplo.com/dados") as resp:
return await resp.json()
O @asynccontextmanager funciona exatamente como @contextmanager, mas com async def e yield.
Padrões Comuns em Bibliotecas Populares
Context managers aparecem em praticamente toda biblioteca Python de qualidade:
# SQLAlchemy — sessões de banco de dados
from sqlalchemy.orm import Session
with Session(engine) as session:
usuario = session.get(Usuario, 1)
session.commit()
# requests — sessões HTTP com conexão persistente
import requests
with requests.Session() as s:
s.headers.update({"Authorization": "Bearer token"})
resposta = s.get("https://api.exemplo.com/dados")
# tempfile — arquivos temporários
import tempfile
with tempfile.NamedTemporaryFile(mode="w", suffix=".csv") as tmp:
tmp.write("nome,idade\nAna,28\n")
tmp.flush()
# arquivo existe enquanto estiver no bloco with
# threading — locks
import threading
lock = threading.Lock()
with lock:
# operação thread-safe
contador += 1
Tratamento de Erros em Context Managers
Um context manager bem escrito deve lidar com exceções de forma previsível:
from contextlib import contextmanager
import logging
logger = logging.getLogger(__name__)
@contextmanager
def operacao_segura(nome: str):
"""Context manager com logging de erros."""
logger.info(f"Iniciando operação: {nome}")
try:
yield
except Exception as e:
logger.error(f"Erro na operação {nome}: {e}")
raise # sempre re-levanta a exceção
else:
logger.info(f"Operação {nome} concluída com sucesso")
finally:
logger.debug(f"Cleanup da operação: {nome}")
with operacao_segura("importar_dados"):
# seu código aqui
dados = processar()
Use logging dentro dos seus context managers para rastrear o ciclo de vida dos recursos. E lembre-se: a menos que tenha um motivo muito específico, sempre re-levante exceções no __exit__.
Criando Context Managers com Dataclasses
Context managers combinam bem com dataclasses para criar gerenciadores de recurso configuráveis:
from dataclasses import dataclass, field
from contextlib import contextmanager
from datetime import datetime
@dataclass
class ConfiguracaoCache:
host: str = "localhost"
porta: int = 6379
timeout: int = 30
_conexao: object = field(init=False, repr=False, default=None)
@contextmanager
def conectar(self):
"""Gerencia conexão com o cache."""
import redis
self._conexao = redis.Redis(
host=self.host, port=self.porta,
socket_timeout=self.timeout
)
try:
yield self._conexao
finally:
self._conexao.close()
self._conexao = None
config = ConfiguracaoCache(host="cache.exemplo.com")
with config.conectar() as cache:
cache.set("chave", "valor")
Considerações Finais
Context managers são fundamentais para escrever código Python robusto e profissional. Eles garantem que recursos sejam sempre liberados, tornam o código mais legível e seguem o princípio de que o cleanup não deve depender da memória do programador.
Use with sempre que trabalhar com recursos que precisam ser liberados — arquivos, conexões, locks, sessões. Crie seus próprios context managers com @contextmanager para operações simples ou com classes quando precisar de mais controle. E para mais dicas de código Python limpo e profissional, confira nosso guia de boas práticas Python em 2026.
Equipe python.dev.br
Contribuidor do Python Brasil — Aprenda Python em Português