Curso de Python / Usar decoradores e gerenciadores de contexto com limpeza segura

Um decorador envolve a chamada de uma função; um gerenciador de contexto organiza a preparação e a limpeza de um bloco with. Use essas ferramentas para separar tarefas repetidas, preservando de forma intencional os argumentos, o valor retornado e a propagação das exceções.

Distinguir o decorador do gerenciador de contexto

A sintaxe @announce faz Python passar a função recém-definida para announce e associar o nome ao objeto retornado. Para este exemplo, equivale a definir a função e depois executar total = announce(total). O decorador não é um comentário: ele troca o objeto chamado quando você usa esse nome.

Já with delimita a utilização de um recurso. Um gerenciador de contexto executa a entrada, entrega um valor ao bloco e trata a saída. A função marcada com @contextmanager deve fornecer exatamente um valor por yield. O código antes desse ponto prepara o recurso; quando o bloco termina, a execução retorna ao gerador.

functools.wraps preserva metadados úteis, como nome e documentação da função original, e mantém a referência __wrapped__. Ele não verifica os tipos dos argumentos nem garante que o decorador tenha preservado o resultado e as regras de erro. Consulte a documentação de functools.wraps para os metadados tratados.

Executar o exemplo completo de função e lista temporária

Crie uma pasta de prática e salve o arquivo wrappers.py em UTF-8 com todo o conteúdo abaixo. O exemplo usa apenas a biblioteca padrão; nenhuma instalação com pip é necessária. No terminal do ambiente virtual do projeto, execute python wrappers.py. Fora de um ambiente virtual no macOS ou Ubuntu, use python3 wrappers.py se esse for o comando do seu interpretador.

from functools import wraps
from contextlib import contextmanager

def announce(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("start")
        return func(*args, **kwargs)
    return wrapper

@contextmanager
def temporary_items():
    items = []
    try:
        yield items
    finally:
        items.clear()
        print("cleared")

@announce
def total(amounts):
    return sum(amounts)

with temporary_items() as items:
    items.extend([3500, 8000])
    print(total(items))
print(total.__name__)
print(items)

O resultado esperado é:

start
11500
cleared
total
[]

temporary_items() cria uma lista vazia, que o as items disponibiliza no bloco. Depois de adicionar os dois valores, a chamada de total(items) passa pelo wrapper: primeiro imprime start, depois retorna a soma, que o print externo mostra como 11500.

Na saída do bloco, finally limpa essa mesma lista e imprime cleared. A variável items continua acessível depois do with; ela aponta para a lista agora vazia. O bloco não cria um novo escopo de variáveis. O nome total mostrado na penúltima linha vem dos metadados preservados por wraps.

Acompanhar retornos e exceções sem ocultar falhas

O return func(*args, **kwargs) repassa argumentos posicionais e nomeados e devolve o resultado original. Se você deixar apenas func(*args, **kwargs), a soma ainda será calculada, mas o wrapper terminará sem devolver esse valor: o chamador receberá None. Uma mensagem de início correta não basta para provar que o decorador funciona.

Se a função original lançar uma exceção, este wrapper também a propaga, pois não a captura. O gerenciador recebe a exceção no ponto do yield, e seu finally ainda executa a limpeza. A linha depois do with só será alcançada se a exceção for tratada em um nível apropriado; a limpeza não transforma automaticamente uma falha em sucesso.

Capturar uma exceção ao redor do yield e não a relançar pode indicar que ela foi tratada, permitindo continuar depois do bloco. Se você apenas registrar o erro, use raise para propagá-lo. A documentação de contextmanager explica esse contrato de entrada, saída e exceção. Os fundamentos estão na aula de tratamento de exceções.

Testar a limpeza e escolher quando abstrair

  1. No exemplo original, altere somente 8000 para 9000. A soma deve passar para 12500, mantendo a limpeza e a lista final vazia. Restaure 8000.
  2. Dentro do bloco, acrescente raise ValueError("teste") logo depois de items.extend(...). A execução deve imprimir cleared e terminar com a exceção; as impressões posteriores não serão executadas. Para inspecionar a lista nesse caso, coloque o bloco inteiro em um try e capture ValueError depois dele, imprimindo items no tratamento. Espere []. Não mova a limpeza para fora do finally.
  3. Remova o teste de erro, tire temporariamente apenas o return do wrapper e execute novamente. A segunda linha deve ser None. Recoloque o return e confirme o resultado original completo.

Essa lista ilustra a duração controlada de um recurso; esvaziá-la não é o mesmo que fechar um arquivo ou desfazer uma transação. Cada recurso exige a operação de limpeza adequada. Prefira o suporte nativo a with quando o objeto já o oferece, como arquivos abertos com open.

Use um decorador quando o comportamento de chamada realmente se repetir e um gerenciador quando houver uma duração clara de uso. Não acrescente tentativas automáticas de repetição apenas por conveniência: repetir uma escrita pode criar registros ou operações duplicadas. Uma tarefa pequena usada uma única vez pode continuar mais clara com um try/finally local.