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.

6 min de leitura Equipe python.dev.br

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 (ou None)
  • exc_val — a instância da exceção (ou None)
  • exc_tb — o traceback (ou None)

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:

  1. Antes do yield — código de setup (equivalente ao __enter__)
  2. O yield — o valor entregue ao as do with
  3. Depois do yield (no finally) — 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.

E

Equipe python.dev.br

Contribuidor do Python Brasil — Aprenda Python em Português