Decoradores Python: Guia Pratico — 2025 | Python Brasil

Aprenda a criar e usar decoradores em Python com exemplos praticos. Domine closures, functools.wraps e decoradores com argumentos no seu codigo.

5 min de leitura Equipe Python Brasil

Decoradores sao uma das funcionalidades mais elegantes do Python. Eles permitem modificar o comportamento de funcoes e classes sem alterar o codigo original. Neste guia pratico, a gente vai entender como decoradores funcionam por baixo dos panos e criar varios exemplos uteis para o dia a dia.

O Que Sao Decoradores?

Um decorador e uma funcao que recebe outra funcao como argumento, adiciona alguma funcionalidade e retorna uma nova funcao. Em termos simples, e um wrapper que envolve uma funcao existente.

A sintaxe com @ e apenas um atalho sintatico. Estas duas formas sao equivalentes:

# Com sintaxe @
@meu_decorador
def minha_funcao():
    pass

# Equivalente sem @
def minha_funcao():
    pass
minha_funcao = meu_decorador(minha_funcao)

Para entender decoradores, primeiro precisamos entender que em Python, funcoes sao objetos de primeira classe. Isso significa que podemos passar funcoes como argumentos, retorna-las de outras funcoes e atribui-las a variaveis.

Criando Seu Primeiro Decorador

Vamos comecar com um decorador simples que registra quando uma funcao e chamada:

import functools

def log_chamada(func):
    """Decorador que registra chamadas de funcao."""
    @functools.wraps(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_chamada
def somar(a, b):
    """Soma dois numeros."""
    return a + b

@log_chamada
def saudacao(nome, idioma="pt"):
    """Retorna uma saudacao."""
    if idioma == "pt":
        return f"Ola, {nome}!"
    return f"Hello, {nome}!"

# Uso
somar(3, 5)
# Chamando somar com args=(3, 5), kwargs={}
# somar retornou 8

saudacao("Maria", idioma="en")
# Chamando saudacao com args=('Maria',), kwargs={'idioma': 'en'}
# saudacao retornou Hello, Maria!

Repare no uso de functools.wraps(func). Sem ele, a funcao decorada perde seu nome e docstring originais. Sempre use functools.wraps nos seus decoradores.

Decorador de Temporizacao

Um caso de uso muito comum e medir o tempo de execucao de funcoes:

import functools
import time

def cronometrar(func):
    """Mede o tempo de execucao de uma funcao."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        inicio = time.perf_counter()
        resultado = func(*args, **kwargs)
        fim = time.perf_counter()
        duracao = fim - inicio
        print(f"{func.__name__} executou em {duracao:.4f} segundos")
        return resultado
    return wrapper

@cronometrar
def processar_dados(n):
    """Simula processamento pesado."""
    total = sum(i ** 2 for i in range(n))
    return total

processar_dados(1_000_000)
# processar_dados executou em 0.1523 segundos

Decoradores com Argumentos

Quando voce precisa que o decorador aceite parametros, e necessario criar mais um nivel de funcao aninhada:

import functools

def repetir(vezes):
    """Decorador que repete a execucao de uma funcao."""
    def decorador(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            resultado = None
            for i in range(vezes):
                print(f"Execucao {i + 1} de {vezes}")
                resultado = func(*args, **kwargs)
            return resultado
        return wrapper
    return decorador

@repetir(vezes=3)
def enviar_notificacao(mensagem):
    """Envia uma notificacao."""
    print(f"Notificacao: {mensagem}")
    return True

enviar_notificacao("Backup concluido")
# Execucao 1 de 3
# Notificacao: Backup concluido
# Execucao 2 de 3
# Notificacao: Backup concluido
# Execucao 3 de 3
# Notificacao: Backup concluido

Decorador de Cache (Memoizacao)

O Python ja oferece functools.lru_cache, mas e instrutivo criar o seu proprio:

import functools

def cache_simples(func):
    """Cache simples para funcoes puras."""
    _cache = {}

    @functools.wraps(func)
    def wrapper(*args):
        if args in _cache:
            print(f"Cache hit para {args}")
            return _cache[args]
        resultado = func(*args)
        _cache[args] = resultado
        return resultado

    wrapper.cache_info = lambda: f"Tamanho do cache: {len(_cache)}"
    wrapper.cache_limpar = lambda: _cache.clear()
    return wrapper

@cache_simples
def fibonacci(n):
    """Calcula o n-esimo numero de Fibonacci."""
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(30))  # 832040
print(fibonacci.cache_info())  # Tamanho do cache: 31

Na pratica, use functools.lru_cache que ja vem otimizado:

from functools import lru_cache

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

Decorador de Validacao

Decoradores sao otimos para validar entradas antes de executar a logica principal:

import functools

def validar_tipos(**tipos_esperados):
    """Valida os tipos dos argumentos de uma funcao."""
    def decorador(func):
        @functools.wraps(func)
        def wrapper(**kwargs):
            for param, tipo in tipos_esperados.items():
                if param in kwargs and not isinstance(kwargs[param], tipo):
                    raise TypeError(
                        f"Parametro '{param}' deve ser {tipo.__name__}, "
                        f"recebeu {type(kwargs[param]).__name__}"
                    )
            return func(**kwargs)
        return wrapper
    return decorador

@validar_tipos(nome=str, idade=int)
def cadastrar_usuario(nome, idade):
    """Cadastra um usuario no sistema."""
    return {"nome": nome, "idade": idade}

# Funciona normalmente
cadastrar_usuario(nome="Ana", idade=28)

# Levanta TypeError
# cadastrar_usuario(nome="Ana", idade="vinte")
# TypeError: Parametro 'idade' deve ser int, recebeu str

Decorador de Retry

Muito util para operacoes que podem falhar temporariamente, como chamadas de rede:

import functools
import time

def retry(tentativas=3, delay=1, excecoes=(Exception,)):
    """Repete a funcao em caso de falha."""
    def decorador(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            ultima_excecao = None
            for tentativa in range(1, tentativas + 1):
                try:
                    return func(*args, **kwargs)
                except excecoes as e:
                    ultima_excecao = e
                    print(f"Tentativa {tentativa}/{tentativas} falhou: {e}")
                    if tentativa < tentativas:
                        time.sleep(delay)
            raise ultima_excecao
        return wrapper
    return decorador

@retry(tentativas=3, delay=2, excecoes=(ConnectionError, TimeoutError))
def buscar_dados_api(url):
    """Busca dados de uma API externa."""
    import requests
    response = requests.get(url, timeout=5)
    response.raise_for_status()
    return response.json()

Empilhando Decoradores

Voce pode aplicar varios decoradores a uma mesma funcao. Eles sao aplicados de baixo para cima:

@cronometrar
@log_chamada
def calcular(x, y):
    return x * y

# Equivale a: cronometrar(log_chamada(calcular))

Decoradores em Classes

Decoradores tambem podem ser implementados como classes usando o metodo __call__:

class ContadorChamadas:
    """Conta quantas vezes uma funcao foi chamada."""

    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.chamadas = 0

    def __call__(self, *args, **kwargs):
        self.chamadas += 1
        print(f"{self.func.__name__} foi chamada {self.chamadas} vezes")
        return self.func(*args, **kwargs)

@ContadorChamadas
def processar(dado):
    return dado.upper()

processar("teste")   # processar foi chamada 1 vezes
processar("outro")   # processar foi chamada 2 vezes

Boas Praticas

Ao trabalhar com decoradores, siga estas recomendacoes:

  • Sempre use functools.wraps para preservar metadados da funcao original
  • Mantenha decoradores com responsabilidade unica, cada um faz apenas uma coisa
  • Documente claramente o que o decorador faz e quais parametros aceita
  • Teste decoradores separadamente, como qualquer outra funcao
  • Evite efeitos colaterais inesperados dentro de decoradores
  • Prefira decoradores do stdlib como lru_cache, property e staticmethod quando possiveis

Conclusao

Decoradores sao uma ferramenta poderosa que torna o codigo Python mais limpo e reutilizavel. Com o entendimento de closures e funcoes de ordem superior, voce pode criar decoradores para logging, cache, validacao, controle de acesso e muito mais. Comece com os exemplos deste guia e adapte-os para as necessidades do seu projeto. A pratica constante vai tornar o uso de decoradores algo natural no seu dia a dia como desenvolvedor Python.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português