Instrução with do Python e Gerenciadores de Contexto
Aprenda como funcionam a instrução with e os gerenciadores de contexto do Python, como criar os seus com __enter__/__exit__ e como usar contextlib.
A instrução with garante que recursos como arquivos, conexões de rede e bloqueios sejam configurados e liberados corretamente — mesmo quando uma exceção interrompe o bloco. O objeto que controla essa configuração e encerramento é chamado de gerenciador de contexto.
Este capítulo explica como a instrução with funciona, quando utilizá-la, como escrever seus próprios gerenciadores de contexto usando __enter__ e __exit__, e como criar versões mais simples com contextlib.contextmanager.
Por que o with Existe
Antes da instrução with, o gerenciamento de recursos exigia escrever blocos try/finally manualmente:
f = open("data.txt", "r", encoding="utf-8")
try:
content = f.read()
finally:
f.close() # must always close, even if read() raisesIsso funciona, mas é verboso, fácil de esquecer e adiciona código repetitivo em torno de cada recurso. A instrução with condensa isso em um único bloco legível e realiza a limpeza automaticamente:
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read()
# f is closed here, no matter what happened inside the blockA cláusula as f vincula o valor do gerenciador de contexto ao nome f. Alguns gerenciadores de contexto não produzem um valor útil; nesse caso, você pode omitir as:
with some_lock:
shared_data.append(item)Como a Instrução with Funciona
Quando Python executa uma instrução with, ele segue esta sequência:
- Avalia a expressão após
with— isso produz o objeto gerenciador de contexto. - Chama o método
__enter__()do gerenciador de contexto. O valor de retorno de__enter__()é vinculado à variávelas(se presente). - Executa o corpo do bloco
with. - Chama o método
__exit__(exc_type, exc_val, exc_tb)do gerenciador de contexto.- Se o bloco foi concluído normalmente, os três argumentos são
None. - Se uma exceção foi levantada, os três argumentos a descrevem.
- Se
__exit__retornar um valor verdadeiro, a exceção é suprimida e a execução continua após o blocowith. Se retornar um valor falso (ouNone), a exceção se propaga.
- Se o bloco foi concluído normalmente, os três argumentos são
Esse protocolo é chamado de protocolo do gerenciador de contexto.
Abrindo Arquivos com with
O uso mais comum do with é o manuseio de arquivos. Os objetos de arquivo nativos do Python implementam o protocolo do gerenciador de contexto, portanto são fechados automaticamente quando o bloco termina:
with open("report.txt", "w", encoding="utf-8") as f:
f.write("Sales: 1 000\n")
f.write("Returns: 23\n")
print(f.closed) # True — file was closed on exitSe uma exceção ocorrer dentro do bloco, o arquivo ainda será fechado:
try:
with open("data.txt", "r", encoding="utf-8") as f:
raise RuntimeError("something went wrong")
except RuntimeError:
pass
print(f.closed) # True — closed despite the exceptionSem with, esquecer f.close() após um erro deixa o descritor de arquivo aberto até que o coletor de lixo seja executado — ou até que o processo encerre — o que pode causar perda de dados ou erros de "too many open files" em programas de longa duração.
Abrindo Múltiplos Recursos ao Mesmo Tempo
Você pode abrir vários recursos em uma única instrução with separando-os com vírgulas (Python 3.1+):
with open("input.txt", "r", encoding="utf-8") as src, \
open("output.txt", "w", encoding="utf-8") as dst:
for line in src:
dst.write(line.upper())Isso é exatamente equivalente a aninhar duas instruções with, mas mantém o nível de indentação plano.
Escrevendo um Gerenciador de Contexto com __enter__ e __exit__
Qualquer classe que defina __enter__ e __exit__ pode ser usada com a instrução with. Veja um exemplo mínimo — um cronômetro que mede quanto tempo o bloco with leva para ser executado:
import time
class Timer:
def __enter__(self):
self._start = time.perf_counter()
return self # bound to the 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
elapsed = time.perf_counter() - self._start
print(f"Elapsed: {elapsed:.4f}s")
return False # do not suppress exceptions
with Timer() as t:
total = sum(range(1_000_000))
# Elapsed: 0.0xxx s
print(total) # 499999500000Pontos principais:
__enter__é executado antes do bloco. Ele retorna o valor vinculado aas t. Retornarselfpermite que o chamador acesset.elapsede outros atributos, se necessário.__exit__é executado após o bloco, mesmo em caso de exceção. RetornarFalse(ouNone) permite que qualquer exceção se propague normalmente.
Suprimindo Exceções em __exit__
Se __exit__ retornar True, a exceção é engolida e a execução continua após o bloco with. Isso é intencional em contextos específicos — por exemplo, um gerenciador de contexto que captura e registra erros sem travar o programa:
class Ignore:
"""Silently ignore any exception raised inside the with block."""
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
print(f"Suppressed: {exc_type.__name__}: {exc_val}")
return True # suppress the exception
with Ignore():
x = 1 / 0 # ZeroDivisionError is caught and ignored
print("execution continues here")
# Suppressed: ZeroDivisionError: division by zero
# execution continues hereUse a supressão de exceções com cuidado — engolir erros silenciosamente pode esconder bugs. A forma idiomática da biblioteca padrão para isso é contextlib.suppress (veja abaixo).
Um Gerenciador de Contexto para Conexão de Banco de Dados
Um exemplo mais realista — gerenciando uma conexão estilo banco de dados que confirma em caso de sucesso e faz rollback em caso de erro:
class ManagedTransaction:
def __init__(self, connection):
self.conn = connection
def __enter__(self):
self.conn.begin()
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.conn.commit()
else:
self.conn.rollback()
return False # always let exceptions propagateO padrão — confirmar em caso de sucesso, fazer rollback em caso de falha — aparece em bibliotecas de banco de dados reais (SQLite, SQLAlchemy e psycopg2 todas o implementam).
contextlib.contextmanager: Gerenciadores de Contexto Baseados em Geradores
Escrever uma classe completa com __enter__ e __exit__ é a abordagem correta para gerenciadores de contexto complexos ou com estado. Para casos mais simples, o decorador contextlib.contextmanager permite expressar a mesma lógica como uma função geradora:
from contextlib import contextmanager
@contextmanager
def managed_open(path, mode="r", encoding="utf-8"):
print(f"Opening {path}")
f = open(path, mode, encoding=encoding)
try:
yield f # everything up to yield is __enter__
finally:
f.close() # everything after yield is __exit__
print(f"Closed {path}")
with managed_open("notes.txt", "w") as f:
f.write("hello\n")
# Opening notes.txt
# Closed notes.txtO protocolo do gerador mapeia diretamente no protocolo do gerenciador de contexto:
- Código antes do
yield→__enter__(configuração). - A expressão
yield→ o valor vinculado à variávelas. - Código após o
yield(geralmente em umfinally) →__exit__(encerramento).
O try/finally ao redor do yield é importante: sem ele, uma exceção dentro do bloco with faria com que o código de encerramento nunca fosse executado.
Exemplo com contextmanager: Diretório de Trabalho Temporário
import os
from contextlib import contextmanager
@contextmanager
def working_directory(path):
original = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(original)
with working_directory("/tmp"):
print(os.getcwd()) # /tmp (or system temp dir)
print(os.getcwd()) # restored to original directoryEsse padrão também está disponível na biblioteca padrão como tempfile.TemporaryDirectory.
Utilitários do contextlib
O módulo contextlib inclui vários gerenciadores de contexto prontos para uso que vale conhecer:
contextlib.suppress
Suprime exceções específicas sem nenhum código repetitivo:
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("temp.txt") # no error even if file does not existEquivalente a um try/except que não faz nada quando a exceção é capturada.
contextlib.nullcontext
Um gerenciador de contexto sem operação, útil quando você quer condicionalmente usar ou não um gerenciador de contexto:
from contextlib import nullcontext
def process(data, lock=None):
ctx = lock if lock is not None else nullcontext()
with ctx:
return sorted(data)Sem nullcontext, você precisaria de uma ramificação if lock: a cada vez.
contextlib.ExitStack
ExitStack permite gerenciar um número dinâmico de gerenciadores de contexto — útil quando o número de recursos não é conhecido até o tempo de execução:
from contextlib import ExitStack
files = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
handles = [
stack.enter_context(open(f, "w", encoding="utf-8"))
for f in files
]
for i, fh in enumerate(handles):
fh.write(f"file {i}\n")
# All three files are closed hereExitStack também é a ferramenta certa quando você precisa adicionar condicionalmente um gerenciador de contexto ou quando deseja adiar a limpeza para um ponto posterior.
Quando Usar with vs. Try/Finally
Use with sempre que:
- Um recurso precisa ser liberado após o uso (arquivos, sockets, bloqueios, cursores de banco de dados).
- Você quiser garantir a limpeza mesmo em caso de exceções.
- A lógica de limpeza é sempre a mesma, independentemente do sucesso ou falha.
Use um try/finally simples apenas quando:
- Você precisar de ações de limpeza diferentes dependendo do tipo de exceção — embora
__exit__também possa fazer isso. - Você estiver escrevendo código compatível com Python 2 (raro hoje em dia).
Na prática, se o objeto suporta o protocolo do gerenciador de contexto, sempre prefira with.
Referência Rápida
| Recurso | O que faz |
|---|---|
with expr as v: | Chama expr.__enter__(), vincula o resultado a v, chama __exit__ ao sair |
| Múltiplos recursos | with A() as a, B() as b: — ambos são limpos mesmo se B() lançar uma exceção |
__enter__(self) | Configuração; o valor de retorno é vinculado à variável as |
__exit__(self, exc_type, exc_val, exc_tb) | Encerramento; retorne True para suprimir a exceção |
@contextmanager | Transforma uma função geradora em um gerenciador de contexto |
contextlib.suppress(E) | Engole a exceção do tipo E sem um try/except |
contextlib.nullcontext() | Substituto quando um gerenciador de contexto é opcional |
contextlib.ExitStack | Gerencia um conjunto dinâmico ou condicional de gerenciadores de contexto |
Capítulos Relacionados
- Python File Handling — usando
with open(...)para todas as operações com arquivos - Python Try Except — tratamento de exceções e try/finally
- Python Decorators —
@contextmanagerusa o mesmo padrão de decorador - Python Generators — como o protocolo de gerador alimenta
@contextmanager - Python Classes and Objects — escrevendo métodos
__enter__e__exit__