Decoradores em Python
Aprenda como funcionam os decoradores em Python: criando os seus próprios, preservando metadados com functools.wraps, empilhando decoradores e casos de uso reais.
Um decorador é uma função que envolve outra função para estender ou modificar seu comportamento sem alterar o código-fonte. Decoradores são um dos recursos mais poderosos e idiomáticos do Python — são o mecanismo por trás de @staticmethod, @classmethod, @property, @functools.lru_cache e de muitos padrões populares em frameworks web.
Esta página explica como os decoradores funcionam, como criar o seu próprio do zero, como passar argumentos para decoradores, como empilhá-los e quando cada padrão é mais útil.
Como Funcionam os Decoradores
Um decorador é simplesmente uma função que recebe outra função como argumento e retorna uma nova função. O Python oferece a sintaxe @ como atalho para aplicá-lo:
@shout
def greet(name):
return f"hello, {name}"Isso é exatamente equivalente a:
def greet(name):
return f"hello, {name}"
greet = shout(greet)A linha @shout instrui o Python: após definir greet, passe-o imediatamente para shout e revinule o nome greet ao que shout retornar. A partir desse ponto, toda chamada a greet(...) passa primeiro pela lógica de shout.
Escrevendo Seu Primeiro Decorador
Um decorador normalmente define uma função wrapper interna que chama a função original e adiciona comportamento extra ao redor dela:
def shout(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
@shout
def greet(name):
return f"hello, {name}"
print(greet("world")) # HELLO, WORLD
print(greet("python")) # HELLO, PYTHONwrapper aceita *args e **kwargs para encaminhar qualquer combinação de argumentos a func sem alterações. Isso torna o decorador compatível com qualquer função, independentemente de sua assinatura — um bom hábito desde o início.
Por Que o Wrapper Deve Retornar a Função Interna
shout termina com return wrapper, não com return wrapper(). Isso é intencional: shout está construindo um novo chamável, não o invocando ainda. Se você escrevesse return wrapper() por engano, o decorador seria executado imediatamente no momento da decoração e greet ficaria vinculado ao valor de retorno de wrapper — uma string — em vez de ao próprio chamável.
Preservando Metadados com functools.wraps
Toda função Python carrega metadados: __name__, __doc__, __module__ e outros. Sem cuidado extra, um decorador substitui a função original por wrapper, perdendo tudo isso:
def shout(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
@shout
def greet(name):
"""Say hello to name."""
return f"hello, {name}"
print(greet.__name__) # wrapper — wrong
print(greet.__doc__) # None — lostCorrija isso aplicando @functools.wraps(func) ao wrapper. Ele copia os metadados da função original para wrapper:
import functools
def shout(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result.upper()
return wrapper
@shout
def greet(name):
"""Say hello to name."""
return f"hello, {name}"
print(greet("world")) # HELLO, WORLD
print(greet.__name__) # greet
print(greet.__doc__) # Say hello to name.Sempre use @functools.wraps em qualquer decorador que você escrever. Sem ele, ferramentas de depuração, geradores de documentação e frameworks de teste verão o nome errado da função. A única exceção é quando você deseja intencionalmente ocultar a identidade original.
Exemplos Práticos de Decoradores
Logger
Registre cada chamada a uma função com seus argumentos e valor de retorno:
import functools
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args={args} kwargs={kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result!r}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
add(3, 5)
# Calling add with args=(3, 5) kwargs={}
# add returned 8Timer
Meça quanto tempo uma função leva para executar:
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.6f}s")
return result
return wrapper
@timer
def slow_sum(n):
return sum(range(n))
total = slow_sum(1_000_000)
print(total) # slow_sum took 0.01xxs then 499999500000time.perf_counter() é a escolha certa aqui porque tem a maior resolução disponível para medições de curta duração.
Memoização (Cache)
Armazene em cache o valor de retorno para cada conjunto único de argumentos, para que a função nunca seja calculada duas vezes para a mesma entrada:
import functools
def memoize(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(10)) # 55
print(fibonacci(30)) # 832040Para código em produção, prefira o @functools.lru_cache ou @functools.cache embutido (Python 3.9+), que tratam casos especiais, segurança de threads e limites de tamanho de cache. A versão manual acima é útil para entender o padrão.
Controle de Acesso
Proteja uma função para que ela só possa ser executada quando uma condição for atendida:
import functools
def require_auth(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if not user.get("is_authenticated"):
raise PermissionError("Authentication required.")
return func(user, *args, **kwargs)
return wrapper
@require_auth
def get_dashboard(user):
return f"Welcome, {user['name']}!"
guest = {"name": "Guest", "is_authenticated": False}
admin = {"name": "Admin", "is_authenticated": True}
try:
print(get_dashboard(guest))
except PermissionError as e:
print(e) # Authentication required.
print(get_dashboard(admin)) # Welcome, Admin!Decoradores com Argumentos
Às vezes você precisa configurar um decorador no momento da decoração — por exemplo, para repetir uma função um número variável de vezes. Decoradores simples não podem receber argumentos extras diretamente porque o Python passa a função, não os argumentos. A solução é uma fábrica de decoradores: uma função que aceita a configuração e retorna um decorador:
import functools
def repeat(n):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say(message):
print(message)
say("hello")
# hello
# hello
# helloLendo de fora para dentro: @repeat(3) primeiro chama repeat(3), que retorna decorator. O Python então aplica decorator a say, que retorna wrapper. Assim, say acaba apontando para wrapper — o mesmo padrão de antes, com o nível extra apenas para levar n ao escopo.
O aninhamento pode parecer intimidador a princípio. Um atalho mental: a função mais externa contém a configuração, a função do meio contém a função sendo decorada e a função mais interna contém a chamada sendo interceptada.
Empilhando Vários Decoradores
Você pode aplicar vários decoradores a uma única função empilhando linhas com @. O Python os aplica de baixo para cima — o decorador mais próximo do def é aplicado primeiro:
import functools
def bold(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return "<b>" + func(*args, **kwargs) + "</b>"
return wrapper
def italic(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return "<i>" + func(*args, **kwargs) + "</i>"
return wrapper
@bold
@italic
def greet(name):
return f"Hello, {name}"
print(greet("Alice")) # <b><i>Hello, Alice</i></b>Equivalente a greet = bold(italic(greet)). italic envolve greet primeiro, depois bold envolve o resultado. A saída mostra que italic é executado mais perto da string bruta e bold envolve o lado de fora.
Decoradores Baseados em Classes
Uma classe também pode ser um decorador — qualquer objeto com um método __call__ é chamável. Decoradores baseados em classes são úteis quando o próprio decorador precisa manter estado entre chamadas:
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Call #{self.count} to {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello()
say_hello()
print(say_hello.count) # 2functools.update_wrapper(self, func) faz o mesmo trabalho que @functools.wraps — copia os metadados da função original para a instância. Após a decoração, say_hello é uma instância de CountCalls, portanto say_hello.count é um acesso de atributo comum.
Quando escolher uma classe em vez de um decorador de função:
- Você precisa de estado persistente (
count,cache, flags). - O decorador tem vários métodos ou lógica auxiliar.
- Você precisa que o objeto decorado seja introspeccionável como um tipo específico.
Armadilhas com Decoradores
Esquecer de chamar a função decorada
Um erro comum para iniciantes é retornar o wrapper mas esquecer de chamar func dentro dele:
def broken(func):
def wrapper(*args, **kwargs):
print("before")
# forgot to call func!
return wrapperA função decorada retorna None silenciosamente em todas as chamadas. Certifique-se sempre de que wrapper chama func(*args, **kwargs) e retorna seu resultado.
Decorar na camada errada
Ao usar decoradores parametrizados, esquecer a chamada externa é um erro frequente:
# Wrong — 'repeat' receives the function, not a count
@repeat # should be @repeat(3)
def say(msg):
print(msg)Isso passa say para repeat onde n é esperado, causando um TypeError quando say é chamada.
A ordem dos decoradores importa
Com decoradores empilhados, a ordem altera o comportamento. @timer antes de @log_calls na mesma função cronometrará a versão já registrada, enquanto o inverso registrará a versão já cronometrada. Pense com cuidado no que você quer que cada camada veja.
Relação com Closures
A função wrapper de um decorador é um closure — ela captura func do escopo envolvente e o mantém vivo mesmo após o retorno da função decoradora externa. Entender closures torna os internos de decoradores óbvios: o objeto cell que contém func é exatamente o que permite que wrapper chame a função original muito tempo depois que shout(greet) foi concluído.
Para a sintaxe de *args e **kwargs usada dentro dos wrappers, veja o capítulo dedicado. Para expressões lambda que combinam bem com decoradores em padrões de ordem superior, veja o capítulo sobre lambda.
Referência Rápida
| Padrão | Quando usar |
|---|---|
wrapper básico | Adicionar comportamento antes/depois de uma função |
@functools.wraps | Sempre — preserva __name__, __doc__ |
| Fábrica de decoradores (3 níveis) | Necessidade de configurar o decorador |
| Decoradores empilhados | Compor múltiplos comportamentos independentes |
| Decorador baseado em classe | Necessidade de estado persistente entre chamadas |
@functools.lru_cache | Memoizar funções puras (embutido, pronto para produção) |