Decoradores e Closures
Decoradores são um dos recursos mais elegantes do Python. Eles permitem "adicionar extras" a funções sem modificar o código original — adicionar logging, temporização, verificações de permissão... Muitos frameworks e bibliotecas usam decoradores extensivamente. Entendê-los é um marco importante no domínio do Python.
1. Closures
Closures são a base dos decoradores. Simplificando: uma função interna lembra variáveis da função externa, mesmo após a função externa ter terminado sua execução.
def make_multiplier(n):
"""Cria uma função de multiplicação"""
def multiplier(x):
return x * n # Função interna lembra a variável externa n
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
print(double(10)) # 20
Mesmo que double e triple venham da mesma função de fábrica make_multiplier, cada uma lembra valores diferentes de n (2 e 3). Isto é um closure — uma função + as variáveis externas que ela lembra.
2. Básico de Decoradores
Um decorador é uma função que recebe uma função como argumento e retorna uma nova função:
def my_decorator(func):
def wrapper():
print("=== Antes da execução da função ===")
func()
print("=== Depois da execução da função ===")
return wrapper
@my_decorator
def say_hello():
print("Olá!")
say_hello()
Saída:
=== Antes da execução da função ===
Olá!
=== Depois da execução da função ===
@my_decorator equivale a say_hello = my_decorator(say_hello). O açúcar sintático torna o código mais limpo.
Exemplo: Decorador de Logging (Dificuldade ⭐⭐)
def log_call(func):
"""Registra chamadas de função"""
def wrapper(*args, **kwargs):
print(f"[LOG] Chamando {func.__name__}({args}, {kwargs})")
result = func(*args, **kwargs)
print(f"[LOG] {func.__name__} retornou {result}")
return result
return wrapper
@log_call
def add(a, b):
return a + b
@log_call
def greet(name, greeting="Olá"):
return f"{greeting}, {name}!"
print(add(3, 5))
print(greet("João Silva"))
Saída:
[LOG] Chamando add((3, 5), {})
[LOG] add retornou 8
8
[LOG] Chamando greet(('João Silva',), {'greeting': 'Olá'})
[LOG] greet retornou Olá, João Silva!
3. functools.wraps: Preservando Informações da Função Original
Decoradores "substituem" a função original, fazendo com que o nome e o docstring da função sejam perdidos. @wraps resolve isso:
from functools import wraps
def my_decorator(func):
@wraps(func) # Preserva informações da função original
def wrapper(*args, **kwargs):
"""Função interna do decorador"""
print("Antes da execução da função...")
return func(*args, **kwargs)
return wrapper
@my_decorator
def say_hello():
"""Uma função de saudação simples"""
print("Olá!")
print(say_hello.__name__) # say_hello (sem @wraps, mostraria wrapper)
print(say_hello.__doc__) # Uma função de saudação simples (sem @wraps, mostraria função interna do decorador)
@wraps ao escrever decoradores. Sem ele, o nome e a documentação da função decorada são perdidos, dificultando a depuração. Importe wraps de functools e aplique @wraps(func) à função wrapper interna.
4. Decoradores Parametrizados
Às vezes, o próprio decorador precisa de parâmetros:
from functools import wraps
def repeat(times=2):
"""Repete a execução um número especificado de vezes"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Olá, {name}!")
greet("João Silva")
Saída:
Olá, João Silva!
Olá, João Silva!
Olá, João Silva!
Exemplo: Decorador de Temporização (Dificuldade ⭐⭐⭐)
import time
from functools import wraps
def timer(func):
"""Calcula o tempo de execução da função"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"[Timer] {func.__name__} levou {elapsed:.4f} segundos")
return result
return wrapper
@timer
def funcao_lenta():
total = 0
for i in range(1000000):
total += i
return total
result = funcao_lenta()
print(f"Resultado: {result}")
Saída (tempo varia por computador):
[Timer] funcao_lenta levou 0.0452 segundos
Resultado: 499999500000
5. Combinando Múltiplos Decoradores
Uma função pode ter vários decoradores empilhados. A ordem de execução é de baixo para cima:
from functools import wraps
def bold(func):
@wraps(func)
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def italic(func):
@wraps(func)
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@bold
@italic
def hello():
return "Olá"
print(hello()) # <b><i>Olá</i></b>
# Ordem de execução: primeiro @italic, depois @bold
# Equivalente a: hello = bold(italic(hello))
Casos de Uso Comuns
- Logging: Registre automaticamente as chamadas e valores de retorno de cada função.
- Temporização de desempenho: Meça o tempo de execução da função para identificar gargalos.
- Verificações de permissão: Frameworks web usam decoradores
@login_requiredpara verificar se usuários estão logados. - Cache:
@functools.lru_cache()armazena em cache automaticamente resultados de funções. - Tentativas: Tente novamente automaticamente um número especificado de vezes em caso de falha de requisição de rede.
❓ Perguntas Frequentes
P:
*argsekwargssão necessários em decoradores? R: Se seu decorador envolve funções com um número desconhecido de parâmetros, sim — use*args,kwargs para passar todos os argumentos. Se estiver envolvendo apenas funções com uma assinatura específica, você pode nomear parâmetros específicos, mas isso é menos genérico. Decoradores genéricos quase sempre usam*args, **kwargs. P: Qual a relação entre decoradores e closures? R: Decoradores são implementados usando closures. Um closure é "uma função que lembra variáveis externas." Decoradores aproveitam closures para que a função wrapper interna possa acessar o parâmetrofuncpassado da função externa. P: Quais decoradores úteis vêm com Python? R:@property(acesso a atributos),@staticmethod(métodos estáticos),@classmethod(métodos de classe),@functools.lru_cache()(cache),@functools.wraps(preservar informações da função),@dataclass(classes de dados). Todos estão na biblioteca padrão.
📖 Resumo
- Closure: uma função interna lembra as variáveis da função externa, mesmo após a função externa ter terminado sua execução
- Decorador: uma função que recebe uma função e retorna uma função; usa açúcar sintático
@ @wraps(func)preserva o__name__e__doc__da função original — sempre use em decoradores- Decoradores parametrizados: três níveis de aninhamento — externo recebe parâmetros, médio recebe função, interno lida com lógica
- Múltiplos decoradores são aplicados de baixo para cima
- Decoradores são "funções de funções" — aprimorando funções sem modificar o código original
📝 Atividades
-
Iniciante (Dificuldade ⭐): Escreva um decorador
print_callque imprime "Chamando função xxx" quando a função executa. -
Intermediário (Dificuldade ⭐⭐): Escreva um decorador
retry(max_attempts=3)que tenta novamente automaticamente o número especificado de vezes quando uma função levanta uma exceção. Se as tentativas se esgotarem, levante a exceção final. Dica: Use um laço for + try-except no wrapper. -
Avançado (Dificuldade ⭐⭐⭐): Escreva um decorador parametrizado
cache_result(ttl=60)que armazena em cache o valor de retorno da função. Dentro dos segundos TTL, chamar com os mesmos parâmetros retorna o resultado em cache; além do TTL, recalcula o valor.



