Os decorators são uma das funcionalidades mais poderosas e elegantes do Python. Eles permitem modificar o comportamento de funções e métodos de forma flexível e reutilizável, sem alterar seu código fonte original. Se você já usou @staticmethod, @classmethod ou @property, você já utilizou decorators!
Neste guia completo, você vai aprender desde o basics até técnicas avançadas de decorators, com exemplos práticos que você pode aplicar imediatamente em seus projetos.
O Que São Decorators?
Em Python, um decorator é uma função que recebe outra função como argumento e estende seu comportamento sem modificar explicitamente seu código. É como colocar um "embrulho" ao redor de uma função para adicionar funcionalidades extras.
A sintaxe de decorators usa o símbolo @ seguido do nome do decorator, posicionado logo acima da definição de uma função:
@meu_decorator
def minha_funcao():
pass
Essa sintaxe é equivalente a:
def minha_funcao():
pass
minha_funcao = meu_decorator(minha_funcao)
Entender essa equivalência é fundamental para compreender como os decorators funcionam "por baixo dos panos".
Decorators Built-in do Python
O Python já vem com diversos decorators nativos que você pode usar imediatamente em seus projetos.
@staticmethod
O decorator @staticmethod define um método estático dentro de uma classe. Métodos estáticos não recebem o parâmetro self (instância) nem cls (classe), sendo úteis para funcionalidades que não precisam acessar atributos da classe.
class Calculadora:
@staticmethod
def somar(a, b):
return a + b
@staticmethod
def multiplicar(a, b):
return a * b
Chamando sem criar instância
print(Calculadora.somar(5, 3)) # Saída: 8
print(Calculadora.multiplicar(4, 2)) # Saída: 8
Según a documentação oficial do Python, métodos estáticos são similares aos métodos de [C++](https://en.wikipedia.org/wiki/C%2B%2B) ou [Java](https://www.wikipedia.org/wiki/Java_(programming_language)). A documentação oficial está disponível em [python.org](https://docs.python.org/3/library/functions.html#staticmethod).
@classmethod
O decorator @classmethod define um método de classe que recebe a classe como primeiro argumento (convencionalmente chamado cls). Isso permite acessar e modificar atributos de classe, criar factory methods, e manipular a própria classe.
class Pessoa:
total_pessoas = 0
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
Pessoa.total_pessoas += 1
@classmethod
def criar_aniversario(cls, nome):
"""Factory method para criar pessoa com 0 anos"""
return cls(nome, 0)
@classmethod
def get_total(cls):
return cls.total_pessoas
@classmethod
def alterar_total(cls, novo_valor):
cls.total_pessoas = novo_valor
p1 = Pessoa("Ana", 25)
p2 = Pessoa.criar_aniversario("João")
print(Pessoa.get_total()) # Saída: 2
Métodos de classe são especialmente úteis para alternative constructors (construtores alternativos), como mostram os exemplos na [PEP 8](https://peps.python.org/pep-0008/) e na [documentação oficial](https://docs.python.org/3/library/functions.html#classmethod).
@property
O decorator @property permite definir métodos que se comportam como atributos, permitindo adicionar lógica de getter, setter e deleter de forma controlada.
class Temperatura:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
@property
def kelvin(self):
return self._celsius + 273.15
@celsius.setter
def celsius(self, valor):
if valor < -273.15:
raise ValueError("Temperatura não pode ser menor que zero absoluto")
self._celsius = valor
temp = Temperatura(25)
print(temp.fahrenheit) # Saída: 77.0
print(temp.kelvin) # Saída: 298.15
temp.celsius = 30
print(temp.celsius) # Saída: 30
O decorator @property é fundamental para encapsulamento em Python. Mais informações podem ser encontradas na [documentação oficial do Python](https://docs.python.org/3/library/functions.html#property) e no [Python Cookbook](https://www.oreilly.com/library/view/python-cookbook-3rd/9781440597339/).
Criando Seu Próprio Decorator
Agora que você conhece os decorators built-in, vamos aprender a criar os seus próprios!
Decorator Simples
Um decorator personalizado é simplesmente uma função que recebe uma função e retorna uma nova função:
def meu_decorator(func):
def wrapper():
print("Antes da função")
func()
print("Depois da função")
return wrapper
@meu_decorator
def diga_ola():
print("Olá!")
diga_ola()
Saída:
Antes da função
Olá!
Depois da função
Decorator com Argumentos
Para criar decorators que aceitam argumentos, precisamos de mais uma camada de funções:
def repetir(vezes):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(vezes):
resultado = func(*args, **kwargs)
return resultado
return wrapper
return decorator
@repetir(3)
def saudacao(nome):
print(f"Olá, {nome}!")
saudacao("Maria")
Saída:
Olá, Maria!
Olá, Maria!
Olá, Maria!
Preservando Metadata da Função
Um problema comum ao criar decorators é perder a metadata da função original (como __name__, __doc__, etc.). Para preservar, usamos functools.wraps:
import functools
def log_decorator(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
print(f"Chamando {func.name}")
resultado = func(args, **kwargs)
print(f"{func.name} finalizada")
return resultado
return wrapper
@log_decorator
def adicionar(a, b):
"""Soma dois números"""
return a + b
print(adicionar.name) # Saída: adicionar (não wrapper!)
print(adicionar.doc) # Saída: Soma dois números
Sem @functools.wraps, o nome seria "wrapper" e a docstring seria perdida. Esta técnica é explicada na [documentação do functools](https://docs.python.org/3/library/functools.html).
Decorators com Argumentos customizados
Vamos criar um decorator mais útil: um que mede o tempo de execução de funções:
import functools
import time
def timer(func):
@functools.wraps(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
@timer
def processar_dados(lista):
total = 0
for item in lista:
total += item ** 2
return total
resultado = processar_dados(range(1000))
print(f"Resultado: {resultado}")
Decorator de Retry
Outro exemplo útil: decorator que tenta executar a função novamente se ela falhar:
import functools
import time
def retry(max_tentativas=3, delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
for tentativa in range(max_tentativas):
try:
return func(args, **kwargs)
except Exception as e:
if tentativa == max_tentativas - 1:
raise
print(f"Tentativa {tentativa + 1} falhou: {e}")
time.sleep(delay)
return wrapper
return decorator
@retry(max_tentativas=3, delay=1)
def conectar_api():
import random
if random.random() > 0.7:
return "Conexão estabelecida!"
raise ConnectionError("API indisponível")
print(conectar_api())
Decorators para Classes
Você também pode aplicar decorators a classes! Um exemplo famoso é o @dataclass do módulo dataclasses:
from dataclasses import dataclass
from typing import List
@dataclass
class Produto:
nome: str
preco: float
quantidade: int = 0
def total(self):
return self.preco * self.quantidade
produto = Produto("Notebook", 2500.00, 3)
print(produto)
print(f"Total: R$ {produto.total()}")
Saída:
Produto(nome='Notebook', preco=2500.0, quantidade=3)
Total: R$ 7500.0
Existem também decorators como @singledispatch (para funções sobrecarregadas), @lru_cache (para memorização), e muito mais. A documentação completa está disponível em [docs.python.org](https://docs.python.org/3/library/functools.html).
Decorators com States
Podemos criar decorators que mantém estado entre chamadas usando closures:
import functools
def cache(func):
@functools.wraps(func)
def wrapper(args):
if args not in wrapper.cache:
wrapper.cache[args] = func(args)
return wrapper.cache[args]
wrapper.cache = {}
return wrapper
@cache
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100)) # Calculado uma vez
print(fibonacci(100)) # Retrievido do cache
print(fibonacci(50)) # Calculado
print(fibonacci(50)) # Retrievido do cache
Stack de Decorators
Você pode aplicar múltiplos decorators a uma mesma função. Eles são aplicados de baixo para cima:
import functools
def decorator_a(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
print("A - Antes")
resultado = func(args, **kwargs)
print("A - Depois")
return resultado
return wrapper
def decorator_b(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
print("B - Antes")
resultado = func(args, **kwargs)
print("B - Depois")
return resultado
return wrapper
@decorator_a
@decorator_b
def minha_funcao():
print("Minha função executando")
minha_funcao()
Saída:
A - Antes
B - Antes
Minha função executando
B - Depois
A - Depois
Classes como Decorators
Além de funções, você pode usar classes como decorators implementando o método __call__:
import functools
class DecoratorDeClasse:
def init(self, func):
self.func = func
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
print("Antes da chamada")
resultado = self.func(*args, **kwargs)
print("Depois da chamada")
return resultado
@DecoratorDeClasse
def funcao_test():
print("Função executando")
funcao_test()
Uso Real: Flask e Django
Decorators são amplamente usados em frameworks web. No Flask, por exemplo:
from functools import wraps
def login_required(f):
@wraps(f)
def decorated_function(*args, *kwargs):
if not current_user.is_authenticated:
return redirect(url_for('login'))
return f(args, **kwargs)
return decorated_function
@app.route('/perfil')
@login_required
def perfil():
return render_template('perfil.html')
No Django, decorators são usados para kontrol de permissões e autenticação. Mais informações sobre decorators em frameworks podem ser encontradas na [documentação do Flask](https://flask.palletsprojects.com/) e [Django](https://docs.djangoproject.com/).
Melhores Práticas
Agora que você domina decorators, aqui estão algumas melhores práticas:
1. Sempre use @functools.wraps
import functools
def meu_decorator(func):
@functools.wraps(func) # Preserve metadata
def wrapper(*args, *kwargs):
return func(args, **kwargs)
return wrapper
2. Documente seus decorators
defdeprecated(reason):
"""Decorator para marcar funções como Deprecated.
Args:
reason: String explicando por que a função está depreciada.
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
import warnings
warnings.warn(
f"{func.__name__} está depreciada: {reason}",
DeprecationWarning,
stacklevel=2
)
return func(*args, **kwargs)
return wrapper
return decorator
@deprecated("Use nova_funcao em vez disso")
def funcao_antiga():
pass
3. Use *args e **kwargs
def validar_args(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Validar argumentos antes
return func(*args, **kwargs)
return wrapper
4. Cuidado com decorators em métodos de instância
class MinhaClasse:
@meu_decorator
def metodo(self):
# O decorator deve usar *args, **kwargs
pass
Decorators Avançados
Decorators com opções de configuração
import functools
def configurar(*config):
def decorator(func):
@functools.wraps(func)
def wrapper(args, **kwargs):
if config.get('log', False):
print(f"Executando {func.name}")
if config.get('cache', False):
Implementar cache
pass
return func(*args, **kwargs)
return wrapper
return decorator
@configurar(log=True, cache=True)
def processar():
pass
Decorator para validação de tipos
import functools
def verificar_tipos(tipos):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
for nome, tipo in tipos.items():
if nome in kwargs:
if not isinstance(kwargs[nome], tipo):
raise TypeError(
f"{nome} deve ser {tipo.name}, "
f"recebido {type(kwargs[nome]).name}"
)
return func(args, kwargs)
return wrapper
return decorator
@verificar_tipos(idade=int, nome=str)
def criar_usuario(nome, idade):
return f"Usuário {nome}, {idade} anos"
print(criar_usuario(nome="João", idade=25)) # OK
print(criar_usuario(nome="João", idade="25")) # TypeError
Quando Usar Decorators
Decorators são ideais para:
- Logging: Registrar chamadas de funções
- Autenticação e autorização: Verificar permissões
- Caching: Armazenar resultados de funções custosas
- Validação: Verificar argumentos antes da execução
- Instrumentação: Medir tempo de execução
- Retry: Tentar novamente em caso de falha
Decorators vs Wrappers
É importante entender a diferença:
Decorators são a sintaxe @decorator aplicada a funções ou classes. Eles são o conceito geral.
Wrappers (ou wrappers) são as funções internas que recebem a função original e adicionam comportamento.
No exemplo:
def decorator(func): # "decorator" é a função decoradora
def wrapper(*args, **kwargs): # "wrapper" é o wrapper
return func(*args, **kwargs)
return wrapper
Próximos Passos
Agora que você domina decorators, continue aprendendo:
- Generators Python — aprenda sobre geradores e iteradores
- Context Managers — gerencie recursos com o protocolo context manager
Decorators são fundamentais para escrever código Pythonico e reutilizável. Pratique criando seus próprios decorators e você verá uma melhoria significativa na qualidade do seu código!
Para mais conteúdo sobre Python e desenvolvimento, continue acompanhando o Universo Python!