Blog

Context Managers e o with em Python

O with existe para que a limpeza aconteça independentemente do que o corpo faça. Retorne cedo, levante uma exceção ou termine normalmente: o código de saída roda do mesmo jeito.

Qualquer objeto com __enter__ e __exit__ pode ser usado assim, então você pode colocar os seus próprios recursos atrás da mesma instrução.

O que os dois métodos fazem

__enter__ roda primeiro e o valor que ele devolve é o que o as liga a um nome. __exit__ roda no fim e recebe qual exceção, se houver, está em curso.

# what with actually guarantees
import io

class Tracked:
    def __init__(self):
        self.events = []
    def __enter__(self):
        self.events.append('enter')
        return self
    def __exit__(self, exc_type, exc, tb):
        self.events.append(f'exit ({exc_type.__name__ if exc_type else "no error"})')
        return False

t = Tracked()
with t as handle:
    handle.events.append('body')
print(t.events)

Ele imprime:

['enter', 'body', 'exit (no error)']

A limpeza roda também na saída por erro

A exceção continua o caminho dela depois que o __exit__ rodou.

# cleanup runs even when the body raises
t2 = Tracked()
try:
    with t2:
        t2.events.append('body')
        raise ValueError('boom')
except ValueError as err:
    t2.events.append(f'caught {err}')
print(t2.events)

Ele imprime:

['enter', 'body', 'exit (ValueError)', 'caught boom']

É esse o objetivo todo. Um try/finally faria o mesmo, mas o with coloca a limpeza ao lado do recurso, em vez de em cada ponto de chamada.

Retornar True engole a exceção

Um valor verdadeiro retornado pelo __exit__ significa “eu tratei isso”.

# returning True from __exit__ swallows the exception
class Swallow:
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc, tb):
        return True

with Swallow():
    raise RuntimeError('this never escapes')
print('still running')

Ele imprime:

still running

Use isso raramente e de propósito. Engolir toda exceção em silêncio é como erros desaparecem.

O atalho do contextlib

Um generator com um único yield vira um context manager. Tudo antes do yield é preparação, tudo depois é limpeza.

# contextlib turns a generator into a context manager
import contextlib

@contextlib.contextmanager
def tag(name, out):
    out.append(f'<{name}>')
    try:
        yield out
    finally:
        out.append(f'</{name}>')

lines = []
with tag('p', lines):
    lines.append('hello')
print(''.join(lines))

Ele imprime:

<p>hello</p>

O try/finally em volta do yield é o que faz a limpeza rodar quando o corpo levanta uma exceção. Sem ele, a exceção pula o resto do generator.

suppress no lugar de try/except/pass

contextlib.suppress diz o que quer dizer e ocupa uma linha.

# contextlib.suppress instead of try/except/pass
with contextlib.suppress(ZeroDivisionError):
    1 / 0
print('suppressed')

Ele imprime:

suppressed

Vários managers de uma vez

Vírgulas aninham eles da esquerda para a direita, e eles fecham na ordem inversa.

# several managers on one line
buf1, buf2 = io.StringIO(), io.StringIO()
with buf1 as a, buf2 as b:
    a.write('one')
    b.write('two')
    print(a.getvalue(), b.getvalue())     # read before the block closes them
print('both closed:', buf1.closed, buf2.closed)

Ele imprime:

one two
both closed: True True

Repare na segunda linha. Assim que o bloco termina, os arquivos estão fechados — então leia o que você precisa lá dentro.

O que lembrar

  • __enter__ devolve o que o as liga a um nome, __exit__ sempre roda.

  • Um retorno verdadeiro do __exit__ engole a exceção.

  • @contextlib.contextmanager mais try/finally é o jeito curto de escrever um.

  • Managers na mesma linha de with fecham na ordem inversa.

Locks, transações de banco, diretórios temporários, cronômetros e configurações mockadas são todos context managers na biblioteca padrão. Se o seu código tem uma preparação e um encerramento correspondente, ele também quer ser um.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.