W3docs

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, PYTHON

wrapper 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     — lost

Corrija 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 8

Timer

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  499999500000

time.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))  # 832040

Para 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
# hello

Lendo 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)  # 2

functools.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 wrapper

A 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ãoQuando usar
wrapper básicoAdicionar comportamento antes/depois de uma função
@functools.wrapsSempre — preserva __name__, __doc__
Fábrica de decoradores (3 níveis)Necessidade de configurar o decorador
Decoradores empilhadosCompor múltiplos comportamentos independentes
Decorador baseado em classeNecessidade de estado persistente entre chamadas
@functools.lru_cacheMemoizar funções puras (embutido, pronto para produção)

Prática

Prática
What does @functools.wraps(func) do inside a decorator?
What does @functools.wraps(func) do inside a decorator?
Prática
Given @bold applied above @italic on a function, which decorator is applied first?
Given @bold applied above @italic on a function, which decorator is applied first?
Prática
What is a decorator factory?
What is a decorator factory?
Was this page helpful?