---
title: "Decorators em Python: O que É e Como Funciona | Python Brasil"
url: "https://python.dev.br/glossario/decorators/"
markdown_url: "https://python.dev.br/glossario/decorators.MD"
description: "Entenda decorators em Python: functools.wraps, decorators de classe, stacking, lru_cache, retry, rate limiting e padrões reais. Guia completo com exemplos."
date: "2025-05-02"
author: ""
---

# Decorators em Python: O que É e Como Funciona | Python Brasil

Entenda decorators em Python: functools.wraps, decorators de classe, stacking, lru_cache, retry, rate limiting e padrões reais. Guia completo com exemplos.


## O que são Decorators?

**Decorators** são uma forma elegante de **modificar ou estender o comportamento de funções e classes** sem alterar seu código-fonte. Eles usam o símbolo `@` e são um dos recursos mais poderosos e utilizados do Python.

Se você já usou Flask ou Django, com certeza já viu decorators como `@app.route()` ou `@login_required`. Em código de produção eles aparecem em logging, autenticação, cache, retry, validação e muito mais.

## Como funcionam internamente

Um decorator é basicamente uma **função que recebe outra função como argumento** e retorna uma nova função. A sintaxe `@decorator` é apenas açúcar sintático para `func = decorator(func)`:

```python
import time
import functools

def medir_tempo(func):
    @functools.wraps(func)  # Preserva metadados da função original
    def wrapper(*args, **kwargs):
        inicio = time.perf_counter()
        resultado = func(*args, **kwargs)
        fim = time.perf_counter()
        print(f"{func.__name__} levou {fim - inicio:.4f} segundos")
        return resultado
    return wrapper

@medir_tempo
def processar_dados(n):
    """Processa n itens."""
    return sum(range(n))

resultado = processar_dados(1_000_000)
# processar_dados levou 0.0231 segundos

# Sem @medir_tempo:
# processar_dados = medir_tempo(processar_dados)
```

## A importância de functools.wraps

Sem `@functools.wraps`, o decorator "mascara" a identidade da função original, quebrando documentação, debugging e inspeção:

```python
def decorator_ruim(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

def decorator_correto(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator_ruim
def minha_funcao():
    """Documentação importante."""
    pass

@decorator_correto
def outra_funcao():
    """Documentação importante."""
    pass

print(minha_funcao.__name__)  # wrapper — ERRADO!
print(minha_funcao.__doc__)   # None — documentação perdida!
print(outra_funcao.__name__)  # outra_funcao — correto
print(outra_funcao.__doc__)   # Documentação importante. — correto
```

`functools.wraps` copia `__name__`, `__doc__`, `__annotations__`, `__qualname__` e `__dict__` da função original para o wrapper. Use **sempre** ao criar decorators.

## Decorators com parâmetros

Para decorators que aceitam argumentos, adicione uma camada extra de função:

```python
import functools

def repetir(vezes=1, delay=0):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            resultado = None
            for i in range(vezes):
                if delay > 0 and i > 0:
                    time.sleep(delay)
                resultado = func(*args, **kwargs)
            return resultado
        return wrapper
    return decorator

@repetir(vezes=3, delay=0.1)
def saudar(nome):
    print(f"Olá, {nome}!")

saudar("Maria")
# Olá, Maria! (impresso 3 vezes, com 0.1s entre cada)
```

## Decorators baseados em classe

Classes com o método `__call__` podem ser usadas como decorators, o que facilita o armazenamento de estado:

```python
import functools

class Retry:
    """Decorator de classe que implementa lógica de retry com estado."""

    def __init__(self, max_tentativas=3, excecoes=(Exception,), delay=1.0):
        self.max_tentativas = max_tentativas
        self.excecoes = excecoes
        self.delay = delay

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            ultima_excecao = None
            for tentativa in range(1, self.max_tentativas + 1):
                try:
                    return func(*args, **kwargs)
                except self.excecoes as e:
                    ultima_excecao = e
                    print(
                        f"[Retry] Tentativa {tentativa}/{self.max_tentativas} "
                        f"falhou: {e}"
                    )
                    if tentativa < self.max_tentativas:
                        time.sleep(self.delay)
            raise ultima_excecao
        return wrapper

@Retry(max_tentativas=3, excecoes=(ConnectionError, TimeoutError), delay=2.0)
def chamar_api(url):
    import random
    if random.random() < 0.7:
        raise ConnectionError("Falha na conexão")
    return f"Dados de {url}"
```

## Decorando classes inteiras

Decorators também podem ser aplicados a classes, não apenas funções:

```python
def singleton(cls):
    """Garante que apenas uma instância da classe exista."""
    instancias = {}

    @functools.wraps(cls)
    def get_instancia(*args, **kwargs):
        if cls not in instancias:
            instancias[cls] = cls(*args, **kwargs)
        return instancias[cls]

    return get_instancia

@singleton
class Configuracao:
    def __init__(self):
        self.debug = False
        self.db_url = "postgresql://localhost/mydb"

cfg1 = Configuracao()
cfg2 = Configuracao()
print(cfg1 is cfg2)  # True — mesma instância

def adicionar_repr(cls):
    """Adiciona __repr__ automático baseado em __dict__."""
    def __repr__(self):
        attrs = ', '.join(f'{k}={v!r}' for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"
    cls.__repr__ = __repr__
    return cls

@adicionar_repr
class Ponto:
    def __init__(self, x, y):
        self.x = x
        self.y = y

print(Ponto(1, 2))  # Ponto(x=1, y=2)
```

## Empilhando decorators

Múltiplos decorators são aplicados de baixo para cima:

```python
def negrito(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return f"<b>{func(*args, **kwargs)}</b>"
    return wrapper

def italico(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return f"<i>{func(*args, **kwargs)}</i>"
    return wrapper

def maiusculo(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).upper()
    return wrapper

@negrito
@italico
@maiusculo
def saudacao(nome):
    return f"olá, {nome}"

# Equivale a: negrito(italico(maiusculo(saudacao)))
print(saudacao("mundo"))  # <b><i>OLÁ, MUNDO</i></b>
```

## Decorators nativos do Python

O Python inclui vários decorators poderosos na biblioteca padrão:

```python
import functools

# @property — transforma método em atributo
class Circulo:
    def __init__(self, raio):
        self.raio = raio

    @property
    def area(self):
        return 3.14159 * self.raio ** 2

# @functools.lru_cache — memoização automática
@functools.lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(50))  # Instantâneo graças ao cache
print(fibonacci.cache_info())  # hits=48, misses=51, maxsize=128, currsize=51

# @functools.cached_property — property calculada uma vez e cacheada
class Dataset:
    def __init__(self, dados):
        self.dados = dados

    @functools.cached_property
    def estatisticas(self):
        print("Calculando estatísticas...")  # Executado apenas uma vez
        return {
            'media': sum(self.dados) / len(self.dados),
            'minimo': min(self.dados),
            'maximo': max(self.dados),
        }

d = Dataset(list(range(1000)))
print(d.estatisticas)  # Calculando...
print(d.estatisticas)  # Sem recalcular!
```

## Padrões reais: rate limiting e memoização

```python
import time
import functools
from collections import deque

def rate_limit(chamadas_por_segundo):
    """Limita a taxa de execução de uma função."""
    intervalo = 1.0 / chamadas_por_segundo
    historico = deque()

    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            agora = time.monotonic()
            # Remove registros antigos
            while historico and agora - historico[0] > 1.0:
                historico.popleft()
            if len(historico) >= chamadas_por_segundo:
                tempo_espera = 1.0 - (agora - historico[0])
                if tempo_espera > 0:
                    time.sleep(tempo_espera)
            historico.append(time.monotonic())
            return func(*args, **kwargs)
        return wrapper
    return decorator

@rate_limit(chamadas_por_segundo=2)
def chamar_api_externa(endpoint):
    print(f"Chamando {endpoint} em {time.monotonic():.2f}")
    return f"Resposta de {endpoint}"

def memoize(func):
    """Memoização personalizada com suporte a argumentos não-hashable."""
    cache = {}

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # Cria chave a partir dos argumentos
        try:
            chave = (args, tuple(sorted(kwargs.items())))
        except TypeError:
            return func(*args, **kwargs)  # Argumento não-hashable

        if chave not in cache:
            cache[chave] = func(*args, **kwargs)
        return cache[chave]

    wrapper.cache_clear = lambda: cache.clear()
    wrapper.cache_size = lambda: len(cache)
    return wrapper
```

## Decorator thread-safe

Em ambientes multi-thread, use locks para proteger estado compartilhado dentro do decorator:

```python
import threading
import functools

def sincronizado(func):
    """Garante execução thread-safe da função."""
    lock = threading.Lock()

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        with lock:
            return func(*args, **kwargs)

    return wrapper

class Contador:
    def __init__(self):
        self._valor = 0

    @sincronizado
    def incrementar(self):
        self._valor += 1
        return self._valor
```

## Erros comuns

O erro mais frequente é esquecer de usar `@functools.wraps`, perdendo metadados. Outro problema comum é criar decorators com estado mutável compartilhado entre chamadas sem sincronização. Também tome cuidado com decorators que capturam variáveis de loop — use argumentos padrão ou closures corretas.

```python
# ERRADO — todas as funções capturam o mesmo 'i'
funcoes = []
for i in range(3):
    def f():
        return i
    funcoes.append(f)

print([f() for f in funcoes])  # [2, 2, 2] — bug!

# CORRETO — captura o valor atual via argumento padrão
funcoes = []
for i in range(3):
    def f(x=i):
        return x
    funcoes.append(f)

print([f() for f in funcoes])  # [0, 1, 2] — correto
```

## Quando usar decorators?

Use decorators quando você precisa de **comportamento transversal** (cross-cutting concerns) que se aplica a múltiplas funções: logging, autenticação, cache, validação, retry, rate limiting, timing. Evite decorators quando a lógica é específica de uma única função — nesse caso, o código inline é mais claro. Ao escrever testes para funções decoradas, lembre-se de que `functools.wraps` preserva a referência à função original em `__wrapped__`, permitindo testar a lógica da função sem a interferência do decorator quando necessário.

## Termos Relacionados

- [Classe](/glossario/classe/) - Decorators são muito usados com classes
- [Flask](/glossario/flask/) - Framework que usa decorators para rotas
- [Context Manager](/glossario/context-manager/) - Outro padrão de design Python
- [Lambda](/glossario/lambda/) - Funções anônimas em Python

> **Padrões similares em outras linguagens**: <a href="https://kotlin.dev.br/blog/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', {source: 'python.dev.br', target: 'kotlin.dev.br', content: 'glossario-decorators'})">Kotlin usa annotations</a> para funcionalidade similar, enquanto <a href="https://rustlang.com.br/blog/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', {source: 'python.dev.br', target: 'rustlang.com.br', content: 'glossario-decorators'})">Rust implementa macros procedurais</a> que oferecem transformações de código em tempo de compilação.
