W3docs

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() raises

Isso 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 block

A 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:

  1. Avalia a expressão após with — isso produz o objeto gerenciador de contexto.
  2. Chama o método __enter__() do gerenciador de contexto. O valor de retorno de __enter__() é vinculado à variável as (se presente).
  3. Executa o corpo do bloco with.
  4. 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 bloco with. Se retornar um valor falso (ou None), a exceção se propaga.

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 exit

Se 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 exception

Sem 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)  # 499999500000

Pontos principais:

  • __enter__ é executado antes do bloco. Ele retorna o valor vinculado a as t. Retornar self permite que o chamador acesse t.elapsed e outros atributos, se necessário.
  • __exit__ é executado após o bloco, mesmo em caso de exceção. Retornar False (ou None) 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 here

Use 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 propagate

O 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.txt

O 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ável as.
  • Código após o yield (geralmente em um finally) → __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 directory

Esse 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 exist

Equivalente 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 here

ExitStack 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

RecursoO que faz
with expr as v:Chama expr.__enter__(), vincula o resultado a v, chama __exit__ ao sair
Múltiplos recursoswith 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
@contextmanagerTransforma 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.ExitStackGerencia um conjunto dinâmico ou condicional de gerenciadores de contexto

Capítulos Relacionados

Prática

Prática
What method does a context manager call when the with block is entered?
What method does a context manager call when the with block is entered?
Prática
What happens when __exit__ returns True?
What happens when __exit__ returns True?
Prática
In a @contextmanager generator, code before the yield statement corresponds to which part of the context manager protocol?
In a @contextmanager generator, code before the yield statement corresponds to which part of the context manager protocol?
Prática
Which contextlib utility suppresses specific exceptions without a try/except block?
Which contextlib utility suppresses specific exceptions without a try/except block?
Was this page helpful?