Decoradores em Python – O que são e como usar

Os decoradores são um dos recursos mais poderosos e elegantes do Python. Eles permitem modificar ou estender o comportamento de funções, métodos e classes sem alterar diretamente o código original. É como “embrulhar” uma função com outra que adiciona funcionalidades extras — uma técnica muito usada em frameworks web, logging, autenticação, caching, validação e muito mais.

Neste artigo, vamos explorar em detalhes:

  • O que são decoradores.

  • Como funcionam funções como objetos de primeira classe.

  • Criando decoradores simples e avançados.

  • Decoradores com argumentos.

  • Aplicações práticas em projetos reais.

  • Decoradores prontos da biblioteca padrão.

  • Boas práticas e armadilhas comuns.


1) Funções como objetos de primeira classe

Em Python, funções são objetos. Isso significa que podemos:

  • Atribuir funções a variáveis.

  • Passar funções como argumento para outras funções.

  • Retornar funções de dentro de funções.

Exemplo básico:

def saudacao():
    return "Olá!"

f = saudacao   # atribuímos a função a uma variável
print(f())     # chama a função normalmente → "Olá!"

Isso é essencial para entender decoradores, já que eles usam exatamente esse recurso.


2) Funções internas (closures)

Podemos definir funções dentro de funções:

def externa():
    def interna():
        print("Função interna chamada!")
    interna()

externa()

Funções internas podem capturar variáveis da função externa (closure):

def multiplicador(fator):
    def interno(valor):
        return valor * fator
    return interno

dobro = multiplicador(2)
print(dobro(10))  # 20

Esse conceito é a base dos decoradores.


3) O que é um decorador?

Um decorador é, essencialmente, uma função que recebe outra função como parâmetro e retorna uma nova função (modificada ou expandida).

Exemplo simples:

def meu_decorador(func):
    def wrapper():
        print("Antes da função")
        func()
        print("Depois da função")
    return wrapper

@meu_decorador
def diz_oi():
    print("Oi!")

diz_oi()

Saída:

Antes da função
Oi!
Depois da função

Note o uso do @meu_decorador — essa é apenas açúcar sintático para:

diz_oi = meu_decorador(diz_oi)

4) Decoradores com argumentos da função original

wrapper precisa receber *args e **kwargs para suportar funções com parâmetros:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print(f"Chamando {func.__name__} com args={args}, kwargs={kwargs}")
        resultado = func(*args, **kwargs)
        print(f"{func.__name__} retornou {resultado}")
        return resultado
    return wrapper

@log_calls
def soma(a, b):
    return a + b

soma(3, 5)

5) Decoradores com argumentos próprios

Às vezes queremos que o decorador receba parâmetros. Para isso, criamos uma função que retorna o decorador:

def repetir(n):
    def decorador(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorador

@repetir(3)
def oi():
    print("Oi!")

oi()

Saída:

Oi!
Oi!
Oi!

6) Mantendo metadados da função original

Um problema: ao decorar funções, o wrapper substitui metadados como __name__ e __doc__.
Solução: usar functools.wraps.

from functools import wraps

def meu_decorador(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

Agora, help(func) e func.__name__ mostram os dados corretos.


7) Decoradores aplicados a métodos

Decoradores funcionam da mesma forma em métodos de classe:

def debug(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[DEBUG] {func.__name__} chamado")
        return func(*args, **kwargs)
    return wrapper

class Calculadora:
    @debug
    def soma(self, a, b):
        return a + b

c = Calculadora()
print(c.soma(2, 3))

8) Decoradores aplicados a classes

Um decorador também pode receber uma classe e retornar uma versão modificada dela:

def add_repr(cls):
    def __repr__(self):
        return f"{cls.__name__}({self.__dict__})"
    cls.__repr__ = __repr__
    return cls

@add_repr
class Pessoa:
    def __init__(self, nome):
        self.nome = nome

p = Pessoa("João")
print(p)  # Pessoa({'nome': 'João'})

9) Aplicações práticas de decoradores

Exemplo de medição de tempo:

import time

def tempo_execucao(func):
    @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}s")
        return resultado
    return wrapper

@tempo_execucao
def tarefa():
    time.sleep(1)

tarefa()

10) Decoradores prontos no Python

Python já oferece decoradores úteis:

Exemplo lru_cache:

from functools import lru_cache

@lru_cache(maxsize=128)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

print(fib(35))  # executa rápido devido ao cache

11) Decoradores aninhados (stacking)

Você pode empilhar decoradores:

@tempo_execucao
@log_calls
def processar():
    return sum(i*i for i in range(10**5))

A ordem importa: o decorador mais próximo da função é aplicado primeiro.


12) Decoradores avançados: registrando plugins

Decoradores também são ótimos para registrar funções automaticamente:

plugins = {}

def registrar(nome):
    def decorador(func):
        plugins[nome] = func
        return func
    return decorador

@registrar("soma")
def soma(a, b):
    return a + b

@registrar("mult")
def mult(a, b):
    return a * b

print(plugins["soma"](2, 3))  # 5
print(plugins["mult"](2, 3))  # 6

Isso é usado em sistemas de comandos, APIs de plugins, frameworks.


13) Boas práticas com decoradores

  • Sempre use functools.wraps para manter metadados.

  • Mantenha decoradores pequenos e focados em uma responsabilidade.

  • Evite lógica complexa dentro de decoradores.

  • Use decoradores aninhados com cautela (pode dificultar debugging).

  • Documente bem decoradores que serão usados por outros desenvolvedores.


Conclusão

Decoradores em Python oferecem uma forma limpa e reutilizável de adicionar comportamentos às funções, métodos e classes. Do logging ao cache, da autorização ao plugin system, eles tornam seu código mais modular, expressivo e poderoso.

Se você ainda não usa decoradores nos seus projetos, experimente começar simples (um medidor de tempo, por exemplo) e vá evoluindo para usos mais avançados — você vai perceber como eles podem deixar seu código muito mais elegante.

Comentários

Postagens mais visitadas deste blog

Gerando Relatórios em PDF com Python (ReportLab e FPDF)

Python para Computação Quântica: Introdução com Qiskit

Estrutura Básica de um Programa C# com exemplos