# Decoradores em Python: o que são, como funcionam e como criar o seu

URL: https://skillaria.top/pt/guides/python/decoradores-python

Decorador em Python é uma função que recebe outra função e devolve uma nova, geralmente um invólucro que acrescenta comportamento antes ou depois da chamada: registro de logs, medição de tempo, cache, verificação de permissões. A notação @decorator acima de def é apenas um atalho para func = decorator(func); para que o invólucro mantenha o nome e a docstring da função original, usa-se functools.wraps.

Um decorador em Python é uma função que recebe uma função e devolve outra — em geral um invólucro em torno da original. Assim se acrescenta comportamento, como registro de logs, medição de tempo, cache ou novas tentativas, sem alterar o código da função. Os decoradores são escritos com a notação `@nome` logo acima da declaração da função e aparecem em todo lugar: no Flask e no FastAPI, no pytest, na biblioteca padrão.

## O que é um decorador em linguagem simples

Imagine um presente e o papel de embrulho. O presente (a função) continua o mesmo, mas por fora aparece uma camada que age primeiro. O decorador é exatamente esse embrulho: na chamada, primeiro executa o código do invólucro, que chama a função original e pode fazer algo com o resultado.

Isso funciona porque, em Python, funções são objetos comuns. Elas podem ser passadas a outras funções, devolvidas por funções e declaradas dentro de funções. Os decoradores são construídos sobre essas três propriedades.

## Como funciona um decorador: o @ é açúcar sintático

Veja um decorador simples, que imprime uma mensagem antes e depois da chamada:

```
def log_calls(func):
    def wrapper():
        print(f"Chamando {func.__name__}")
        result = func()
        print(f"{func.__name__} concluída")
        return result
    return wrapper

@log_calls
def hello():
    print("Olá!")

hello()
```

```
Chamando hello
Olá!
hello concluída
```

A notação `@log_calls` acima do `def` é totalmente equivalente à linha que vem depois da declaração:

```
def hello():
    print("Olá!")

hello = log_calls(hello)
```

Agora o nome `hello` aponta para o `wrapper`, e a função original vive dentro do closure — o `wrapper` guarda a variável `func` da função externa. Um detalhe importante: o decorador é aplicado uma única vez, no momento em que o `def` é executado, ou seja, na importação do módulo; já o invólucro é executado a cada chamada.

## Closures e decoradores

Closure é uma função interna que guarda as variáveis da função externa mesmo depois de ela ter terminado. Sem closures não existiriam decoradores: é justamente assim que o `wrapper` tem acesso a `func`. No closure também é possível guardar estado, por exemplo um contador de chamadas:

```
def count_calls(func):
    calls = 0
    def wrapper(*args, **kwargs):
        nonlocal calls
        calls += 1
        print(f"{func.__name__}: chamada nº {calls}")
        return func(*args, **kwargs)
    return wrapper
```

## Como escrever um decorador em Python: modelo universal

O primeiro exemplo tem duas falhas: o invólucro não aceita argumentos e «engole» o nome da função original. Um modelo funcional, com o qual vale começar qualquer decorador, fica assim:

```
import functools

def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # código antes da chamada
        result = func(*args, **kwargs)
        # código depois da chamada
        return result
    return wrapper
```

Três regras do modelo:

1. **`*args, **kwargs`** — o invólucro aceita quaisquer argumentos e os repassa sem alterações.
2. **`return result`** — não esquecer de devolver o resultado, senão a função decorada sempre devolverá `None`.
3. **`@functools.wraps(func)`** — transferir os metadados da função original para o invólucro.

Exemplo de decorador: medição do tempo de execução.

```
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__} levou {elapsed:.3f} s")
        return result
    return wrapper

@timer
def slow_sum(n):
    return sum(range(n))

slow_sum(10_000_000)   # slow_sum levou 0.142 s
```

## Para que serve functools.wraps

Sem `wraps`, a função decorada perde o nome, a docstring e outros atributos — de fora se vê o `wrapper`:

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

@plain
def add(a, b):
    """Soma dois números."""
    return a + b

print(add.__name__, add.__doc__)   # wrapper None
```

Com `@functools.wraps(func)` o invólucro recebe `__name__`, `__doc__`, `__module__`, `__qualname__` e `__annotations__` da função original, e no atributo `__wrapped__` fica a referência ao original. Isso é importante para depuração e para os tracebacks, para `help()`, para frameworks que registram funções pelo nome e para `inspect.signature`, que mostra a assinatura real.

## Decorador com parâmetros (com argumentos)

Às vezes o decorador precisa de configurações: `@retry(times=3)`, `@app.route("/")`. Para isso acrescenta-se mais um nível de aninhamento. A função externa recebe os parâmetros e devolve o decorador de verdade — esse tipo de construção é chamado de fábrica de decoradores.

```
import functools

def repeat(times):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def ping():
    print("ping")

ping()   # ping, ping, ping — três linhas
```

Ordem das chamadas: `repeat(times=3)` devolve `decorator`, que é aplicado a `ping` e devolve `wrapper`. Em outras palavras, `@repeat(times=3)` equivale a `ping = repeat(times=3)(ping)`.

Um exemplo prático — repetir em caso de erro:

```
import functools
import time

def retry(times=3, delay=1.0, exceptions=(Exception,)):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, times + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions:
                    if attempt == times:
                        raise
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(times=5, delay=0.5, exceptions=(ConnectionError,))
def fetch_rate():
    ...
```

## Vários decoradores na mesma função

Decoradores podem ser empilhados. Eles são aplicados de baixo para cima: o mais próximo do `def` é o primeiro.

```
@timer
@log_calls
def report():
    ...

# o mesmo que:
report = timer(log_calls(report))
```

Na chamada, a camada externa (`timer`) é executada primeiro, por isso ela também medirá o tempo de trabalho de `log_calls`. A ordem importa, por exemplo, quando um decorador verifica permissões de acesso e outro faz cache do resultado.

## Decoradores padrão e embutidos do Python

Muitos decoradores úteis já estão na linguagem e na biblioteca padrão:

| Decorador | O que faz |
| --- | --- |

| `@property` | transforma um método em atributo somente de leitura; com `@x.setter` — em atributo controlado |

| `@staticmethod` | método de classe sem `self`, função comum dentro do namespace da classe |

| `@classmethod` | o método recebe a classe `cls` em vez da instância — assim se escrevem construtores alternativos |

| `@functools.cache`, `@functools.lru_cache` | guardam em cache os resultados por argumentos |

| `@functools.wraps` | copia os metadados da função para o invólucro |

| `@dataclasses.dataclass` | gera `__init__`, `__repr__` e `__eq__` para a classe |

| `@contextlib.contextmanager` | transforma um gerador em gerenciador de contexto para o `with` |

### Cache: functools.cache e lru_cache

O decorador de cache memoriza o resultado para cada conjunto de argumentos e, numa nova chamada, devolve-o sem recalcular. O exemplo clássico são os números de Fibonacci recursivos, que sem cache demoram um tempo exponencial:

```
import functools

@functools.cache
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

print(fib(100))   # 354224848179261915075
```

`functools.cache` (surgiu no Python 3.9) guarda todos os resultados sem limite. Já `@functools.lru_cache(maxsize=128)` mantém apenas os registros mais recentes e descarta os antigos — é mais seguro para serviços que funcionam por muito tempo. As estatísticas são vistas com `fib.cache_info()`, e o cache é limpo com `fib.cache_clear()`. Os argumentos dessas funções precisam ser hasheáveis: uma lista como argumento gera `TypeError: unhashable type: 'list'`. E cachear vale apenas para funções puras — aquelas que, com os mesmos argumentos, sempre devolvem o mesmo resultado e não têm efeitos colaterais.

### O que faz o decorador property

`@property` permite acessar um método como se fosse atributo, sem parênteses, e, se necessário, controlar a atribuição de valores:

```
class Account:
    def __init__(self, balance):
        self._balance = balance

    @property
    def balance(self):
        return self._balance

    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError("O saldo não pode ser negativo")
        self._balance = value

acc = Account(100)
print(acc.balance)   # 100
acc.balance = -5     # ValueError
```

### staticmethod e classmethod

```
class Money:
    def __init__(self, cents):
        self.cents = cents

    @classmethod
    def from_reais(cls, reais):
        return cls(round(reais * 100))

    @staticmethod
    def is_valid(value):
        return value >= 0

m = Money.from_reais(12.5)
print(m.cents, Money.is_valid(-1))   # 1250 False
```

É o `@classmethod` que permite ao método trabalhar com a própria classe, e não com as instâncias: `from_reais` cria corretamente também um objeto de uma subclasse.

## Decoradores de classes e classes como decoradores

Um decorador pode ser aplicado a uma classe: ele recebe a classe e devolve a classe (a mesma ou uma alterada). É assim que funciona o `@dataclass`. Um exemplo simples — um registro de classes:

```
registry = {}

def register(cls):
    registry[cls.__name__] = cls
    return cls

@register
class CsvExporter:
    pass

print(registry)   # {'CsvExporter': }
```

O caso inverso é uma classe usada como decorador. Serve qualquer objeto com o método `__call__`; o estado é convenientemente guardado nos atributos da instância:

```
import functools

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.calls = 0

    def __call__(self, *args, **kwargs):
        self.calls += 1
        return self.func(*args, **kwargs)

@CountCalls
def tick():
    pass

tick(); tick()
print(tick.calls)   # 2
```

## Decorador assíncrono

Para funções `async def` o invólucro também deve ser uma corrotina e aguardar o resultado com `await`; caso contrário, será devolvido um objeto de corrotina ainda não executado:

```
import functools

def log_async(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        print(f"Início de {func.__name__}")
        result = await func(*args, **kwargs)
        print(f"Concluído {func.__name__}")
        return result
    return wrapper
```

## Para que servem os decoradores em Python

- **Lógica transversal** — logs, métricas e medição de tempo sem copiar código para cada função.
- **Cache** — `@functools.cache` para cálculos caros e puros.
- **Verificações** — permissões, validação de argumentos, autorização em handlers web.
- **Registro** — rotas no Flask e no FastAPI (`@app.get("/")`), fixtures do pytest.
- **Confiabilidade** — novas tentativas, timeouts, tratamento de erros.

A principal vantagem é a separação de responsabilidades: a função cuida da sua tarefa, e o comportamento auxiliar é acoplado com uma linha e removido com a mesma facilidade.

## Erros típicos

- **O invólucro não devolve o resultado** — a função depois de decorada retorna `None`.
- **Sem `*args, **kwargs`** — `TypeError` ao chamar com argumentos.
- **Esqueceram o `wraps`** — `__name__` e docstring errados, confusão nos logs.
- **Parênteses na fábrica** — `@repeat` em vez de `@repeat(3)`: a função cai no parâmetro `times`.
- **Código pesado no corpo do decorador** — ele roda na importação do módulo, e não na chamada.

## Resumo

Decoradores em Python são funções de ordem superior: recebem uma função, devolvem um invólucro, e o `@` é apenas uma forma curta de atribuição. Comece pelo modelo com `*args, **kwargs`, `return result` e `functools.wraps`, e para configurações acrescente um nível de fábrica. Funções, closures e anotações, sobre os quais se apoiam os decoradores, são praticados no curso de Python.

## FAQ

**Dá para remover um decorador de uma função?**

Se o decorador usa functools.wraps, o original fica disponível pelo atributo __wrapped__: func.__wrapped__(...) chama a função sem o invólucro. Caso contrário, resta apenas tirar a linha com @ do código.

**Quando o código do decorador é executado?**

O corpo do próprio decorador roda uma vez, na declaração da função, geralmente durante a importação do módulo. O código dentro de wrapper roda a cada chamada da função decorada.

**Dá para decorar métodos de classe?**

Sim, um decorador comum com *args, **kwargs funciona também com métodos: o self simplesmente será o primeiro elemento de args.

**Em que um decorador difere da herança?**

A herança altera o comportamento de uma classe por meio de uma hierarquia, enquanto o decorador envolve de fora uma função ou classe específica e combina-se facilmente com outros decoradores, sem mudar o código.


---
Source: Skillaria.Top (https://skillaria.top)
