Decoradores: conceito e implementação
1. O que são Decoradores?
Decoradores são uma das funcionalidades mais elegantes e poderosas do Python. Em essência, um decorador é uma função que recebe outra função como argumento e retorna uma nova função com comportamento estendido ou modificado. Pense neles como "embrulhos" que adicionam funcionalidades extras a funções existentes sem modificar seu código interno.
A sintaxe básica utiliza o símbolo @ como açúcar sintático, tornando a aplicação de decoradores limpa e intuitiva:
@meu_decorador
def minha_funcao():
pass
2. Funções como Objetos de Primeira Classe
Para entender decoradores, é fundamental compreender que em Python funções são objetos de primeira classe. Isso significa que funções podem ser:
- Atribuídas a variáveis
- Passadas como argumentos para outras funções
- Retornadas de outras funções
def saudacao(nome):
return f"Olá, {nome}!"
# Atribuindo a uma variável
minha_funcao = saudacao
print(minha_funcao("Maria")) # Olá, Maria!
# Passando como argumento
def executar_funcao(func, arg):
return func(arg)
print(executar_funcao(saudacao, "João")) # Olá, João!
# Retornando de outra função
def criar_saudacao(idioma):
def saudacao_pt(nome):
return f"Olá, {nome}!"
def saudacao_en(nome):
return f"Hello, {nome}!"
return saudacao_pt if idioma == "pt" else saudacao_en
3. Construindo um Decorador Manualmente
Vamos construir um decorador timer que mede o tempo de execução de uma função:
import time
def timer(func):
def wrapper(*args, **kwargs):
inicio = time.time()
resultado = func(*args, **kwargs)
fim = time.time()
print(f"{func.__name__} executou em {fim - inicio:.4f} segundos")
return resultado
return wrapper
# Aplicação manual
def calcular_soma(n):
return sum(range(n))
calcular_soma = timer(calcular_soma)
print(calcular_soma(1000000))
A função wrapper substitui a função original, adicionando a funcionalidade de medição de tempo antes e depois da execução.
4. Açúcar Sintático com @
O Python oferece uma sintaxe mais elegante usando o símbolo @:
@timer
def calcular_soma(n):
return sum(range(n))
# Equivalente a: calcular_soma = timer(calcular_soma)
print(calcular_soma(1000000))
Vamos criar outro exemplo prático, um decorador @logger:
def logger(func):
def wrapper(*args, **kwargs):
print(f"Chamando {func.__name__} com args={args}, kwargs={kwargs}")
resultado = func(*args, **kwargs)
print(f"{func.__name__} retornou {resultado}")
return resultado
return wrapper
@logger
def dividir(a, b):
return a / b
dividir(10, 2)
# Saída:
# Chamando dividir com args=(10, 2), kwargs={}
# dividir retornou 5.0
5. Decoradores com Argumentos
Para criar decoradores que aceitam parâmetros, precisamos de uma camada extra de aninhamento:
def repetir(vezes):
def decorador(func):
def wrapper(*args, **kwargs):
for _ in range(vezes):
resultado = func(*args, **kwargs)
return resultado
return wrapper
return decorador
@repetir(3)
def saudacao(nome):
print(f"Olá, {nome}!")
saudacao("Ana")
# Saída:
# Olá, Ana!
# Olá, Ana!
# Olá, Ana!
A estrutura é: função externa → função interna → wrapper. A função externa recebe os argumentos do decorador e retorna a função decoradora propriamente dita.
6. Preservando Metadados com functools.wraps
Um problema comum ao usar decoradores é a perda dos metadados da função original (nome, docstring, assinatura):
def meu_decorador(func):
def wrapper(*args, **kwargs):
"""Wrapper interno"""
return func(*args, **kwargs)
return wrapper
@meu_decorador
def minha_funcao():
"""Documentação importante"""
pass
print(minha_funcao.__name__) # wrapper (perdeu o nome original)
print(minha_funcao.__doc__) # Wrapper interno (perdeu a docstring)
A solução é usar @functools.wraps:
import functools
def meu_decorador(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""Wrapper interno"""
return func(*args, **kwargs)
return wrapper
@meu_decorador
def minha_funcao():
"""Documentação importante"""
pass
print(minha_funcao.__name__) # minha_funcao
print(minha_funcao.__doc__) # Documentação importante
7. Empilhamento de Decoradores
Podemos aplicar múltiplos decoradores a uma mesma função. A ordem de execução é de baixo para cima (do mais próximo da função para o mais externo):
@timer
@logger
def processar_dados(n):
return sum(range(n))
# Equivalente a: processar_dados = timer(logger(processar_dados))
processar_dados(100000)
# Ordem de execução:
# 1. timer é o decorador mais externo
# 2. logger é aplicado primeiro à função original
# 3. timer é aplicado ao resultado de logger(processar_dados)
8. Casos de Uso Comuns e Boas Práticas
Controle de acesso e autenticação:
def requer_autenticacao(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if not usuario_autenticado():
raise PermissionError("Usuário não autenticado")
return func(*args, **kwargs)
return wrapper
Cache de resultados (memoização):
def memoizar(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoizar
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
Validação de argumentos:
def validar_positivo(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for arg in args:
if isinstance(arg, (int, float)) and arg < 0:
raise ValueError(f"Argumento negativo não permitido: {arg}")
return func(*args, **kwargs)
return wrapper
Boas práticas:
- Use decoradores para separar preocupações transversais (logging, timing, validação)
- Sempre use @functools.wraps para preservar metadados
- Evite decoradores excessivamente complexos que prejudiquem a legibilidade
- Documente claramente o comportamento dos decoradores
- Considere criar classes de decoradores para casos mais complexos
Decoradores são ferramentas poderosas que, quando usados com moderação e clareza, podem tornar seu código Python mais elegante, reutilizável e fácil de manter.
Referências
- PEP 318 – Decorators for Functions and Methods — Documentação oficial que introduziu decoradores no Python
- Python Documentation: Decorators — Definição oficial e exemplos na documentação do Python
- Primer on Python Decorators – Real Python — Tutorial abrangente sobre decoradores com exemplos práticos
- functools.wraps Documentation — Documentação oficial da função wraps para preservar metadados
- Decorators in Python – GeeksforGeeks — Guia detalhado com exemplos de diferentes tipos de decoradores
- Python Decorators 101 – The Hitchhiker's Guide to Python — Guia de estilo e boas práticas para uso de decoradores