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:

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!