Menu

Context managers em Python: a instrução with explicada

O que a instrução with de fato faz — limpeza automática para arquivos, locks, conexões de banco e qualquer outra coisa que precisa de fechamento confiável.

A instrução que se limpa sozinha

Todo recurso que você abre num programa — um arquivo, uma conexão de rede, um handle de banco de dados, um lock — precisa ser fechado quando você acaba. Esqueça e você vaza memória, segura locks que bloqueiam outros processos ou corrompe arquivos em crash. A instrução with do Python cuida disso para você.

O padrão com que todo mundo começa é ler um arquivo:

with open("notes.txt") as f:
    contents = f.read()
    print(contents)

Duas coisas acontecem automaticamente. Na entrada, open() te dá um objeto de arquivo amarrado a f. Na saída — seja o bloco terminando normalmente, retornando cedo ou lançando — o Python chama f.close() para você.

É isso. Esse é o ponto todo do with.

O que o "with" substitui

Antes de context managers, o código equivalente seguro era um try/finally:

f = open("notes.txt")
try:
    contents = f.read()
    print(contents)
finally:
    f.close()

Cinco linhas de cerimônia para "leia um arquivo e feche quando terminar". Multiplique isso por cada open num programa maior e você começa a ver o apelo. with é mais curto, mais difícil de errar e impossível de esquecer a limpeza.

Abrindo vários recursos

Você pode amarrar vários context managers em um with:

with open("input.txt") as src, open("output.txt", "w") as dst:
    dst.write(src.read().upper())

Os dois arquivos abrem na entrada, os dois fecham na saída. Se o primeiro open dá certo e o segundo lança, o Python ainda fecha o primeiro — a maquinaria lida com setup parcial corretamente.

Para listas mais longas de recursos, a forma com parênteses (Python 3.10+) é mais clara:

with (
    open("a.txt") as a,
    open("b.txt") as b,
    open("c.txt") as c,
):
    ...

O que um context manager de fato é

Qualquer objeto que define __enter__ e __exit__ é um context manager. O protocolo é bobo:

  • __enter__(self) roda quando o bloco with começa. Seu valor de retorno é o que as nome amarra.
  • __exit__(self, exc_type, exc_value, traceback) roda quando o bloco termina, seja como for. Se uma exceção causou a saída, a info da exceção é passada para que o context manager possa inspecionar ou suprimir.

Aqui vai um mínimo que cronometra o bloco que envolve:

main.py
Output
Click Run to see the output here.

with Timer(): cria o objeto, chama o __enter__, roda o corpo, chama o __exit__. Sem arquivo, sem lock — só um pequeno wrapper em torno de "faça algo, meça quanto demorou".

O atalho contextlib.contextmanager

Definir uma classe para cada context manager é mais pesado do que precisa. contextlib.contextmanager transforma uma função geradora num context manager — um yield separa o "antes" do "depois":

main.py
Output
Click Run to see the output here.

Tudo antes do yield é o comportamento __enter__. Tudo depois é o __exit__. O try/finally faz a limpeza rodar mesmo se o corpo lança.

A maioria dos context managers customizados que você vai escrever encaixam nesse formato. Recorra à forma com decorador primeiro; desça para uma classe só quando precisa de algo que a forma com gerador não expressa.

Mudando algo temporariamente

Um formato comum: configure algo, use, restaure. Context managers expressam isso de forma limpa:

main.py
Output
Click Run to see the output here.

Qualquer padrão "configure, depois restaure" — env vars, verbosidade de logging, feature flags, fixtures de teste — encaixa num context manager naturalmente. Quem chama não precisa lembrar de restaurar nada.

Suprimindo exceções

O método __exit__ pode retornar True para dizer ao Python "eu tratei a exceção; engula". Isso é raro e geralmente cheiro ruim, mas é como contextlib.suppress funciona:

main.py
Output
Click Run to see the output here.

suppress(FileNotFoundError) transforma o FileNotFoundError em no-op. Use para operações genuinamente opcionais — "tente isto, não ligo se não funcionar". Não use para silenciar exceções sobre as quais você não pensou.

Outros context managers que você vai encontrar

Context managers aparecem por toda a biblioteca padrão quando você começa a olhar:

import threading
from pathlib import Path

# Locks — guarantee release even if the critical section raises.
lock = threading.Lock()
with lock:
    ...

# tempfile — delete the temp file when you're done.
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
    path = Path(tmp) / "scratch.txt"
    path.write_text("hello")

# Database connections — close the connection (or end the transaction).
import sqlite3
with sqlite3.connect(":memory:") as conn:
    conn.execute("CREATE TABLE t (x INTEGER)")

Bibliotecas de terceiros seguem as mesmas convenções. Quando você vê with alguma_coisa as x:, quase sempre significa "use x pela duração deste bloco, e limpe depois".

Quando não usar with

  • Quando você não tem setup e teardown de fato. Envolver código arbitrário num context manager sem razão adiciona ruído.
  • Quando você precisa do recurso em muitos blocos não relacionados. Manter um with aberto pela vida inteira de um script longo pode esconder qual é o escopo real da limpeza. Considere uma classe que possua o recurso.
  • Quando um decorador encaixa melhor. Alguns padrões repetidos (retry, log, timer) leem mais natural como @decorator numa função do que como with ...: dentro. Escolha o que lê melhor no ponto de chamada.

Na maior parte do tempo, with é certo. As exceções raras são fáceis de identificar quando você está procurando.

Próxima: trabalhando com arquivos de verdade

Você agora sabe a mecânica por trás de with open(...) as f: — que é o contexto em que vai usar em noventa por cento das vezes. O próximo capítulo coloca isso para trabalhar lendo, escrevendo e navegando arquivos no disco.

Perguntas frequentes

O que with open faz em Python?

with open(path) as f: abre o arquivo e amarra a f pela duração do bloco. Quando o bloco termina — normalmente ou por exceção — o Python fecha o arquivo automaticamente. Você não precisa de f.close(); a instrução with garante.

Por que usar with em vez de open() puro?

Porque with fecha o arquivo mesmo quando uma exceção dispara no meio do bloco. open() puro deixa você responsável por lembrar de close() em todo caminho de código, incluindo caminhos de erro. with é mais seguro e mais curto.

Como abro vários arquivos com uma instrução with?

Separe os context managers por vírgula: with open('a.txt') as a, open('b.txt') as b:. Os dois arquivos são abertos na entrada e fechados na saída, em ordem inversa. Isso substitui instruções with aninhadas quando você precisa de vários recursos ao mesmo tempo.

Aprenda a programar com o Coddy

COMEÇAR