Python: Decoradores e Closures
Última atualização: 2026-09-22
Decoradores são um dos recursos mais elegantes do Python. Eles permitem "adicionar extras" a funções sem modificar o código original — adicionar logging, temporização, verificações de permissão... Muitos frameworks e bibliotecas usam decoradores extensivamente. Entendê-los é um marco importante no domínio do Python.
1. Closures
Closures são a base dos decoradores. Simplificando: uma função interna lembra variáveis da função externa, mesmo após a função externa ter terminado sua execução.
def make_multiplier(n):
"""Cria uma function de multiplicação"""
def multiplier(x):
return x * n # Função interna lembra a variable externa n
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
print(double(10)) # 20
Mesmo que double e triple venham da mesma função de fábrica make_multiplier, cada uma lembra valores diferentes de n (2 e 3). Isto é um closure — uma função + as variáveis externas que ela lembra.
2. Básico de Decoradores
Um decorador é uma função que recebe uma função como argumento e retorna uma nova função:
def my_decorator(func):
def wrapper():
print("=== Antes da execução da function ===")
func()
print("=== Depois da execução da function ===")
return wrapper
@my_decorator
def say_hello():
print("Olá!")
say_hello()
Saída:
=== Antes da execução da function ===
Olá!
=== Depois da execução da function ===
@my_decorator equivale a say_hello = my_decorator(say_hello). O açúcar sintático torna o código mais limpo.
▶ Exemplo: Decorador de Logging (Dificuldade ⭐⭐)
▶ ExperimenteSaída:
[LOG] Chamando add((3, 5), {})
[LOG] add retornou 8
8
[LOG] Chamando greet(('João Silva',), {'greeting': 'Olá'})
[LOG] greet retornou Olá, João Silva!
3. functools.wraps: Preservando Informações da Função Original
Decoradores "substituem" a função original, fazendo com que o nome e o docstring da função sejam perdidos. @wraps resolve isso:
from functools import wraps
def my_decorator(func):
@wraps(func) # Preserva informações da function original
def wrapper(*args, **kwargs):
"""Função interna do decorador"""
print("Antes da execução da function...")
return func(*args, **kwargs)
return wrapper
@my_decorator
def say_hello():
"""Uma function de saudação simples"""
print("Olá!")
print(say_hello.__name__) # say_hello (sem @wraps, mostraria wrapper)
print(say_hello.__doc__) # Uma function de saudação simples (sem @wraps, mostraria function interna do decorador)
@wraps ao escrever decoradores. Sem ele, o nome e a documentação da função decorada são perdidos, dificultando a depuração. Importe wraps de functools e aplique @wraps(func) à função wrapper interna.
4. Decoradores Parametrizados
Às vezes, o próprio decorador precisa de parâmetros:
from functools import wraps
def repeat(times=2):
"""Repete a execução um número especificado de vezes"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Olá, {name}!")
greet("João Silva")
Saída:
Olá, João Silva!
Olá, João Silva!
Olá, João Silva!
▶ Exemplo: Decorador de Temporização (Dificuldade ⭐⭐⭐)
▶ ExperimenteSaída (tempo varia por computador):
[Timer] funcao_lenta levou 0.0452 segundos
Resultado: 499999500000
▶ Exemplo: Decorador de Temporização Simples (Dificuldade ⭐)
▶ ExperimenteSaída:
[sum_range] levou 0.008123 segundos
Resultado da soma: 5000050000
[greet] levou 0.000001 segundos
Olá, Alice!
5. Combinando Múltiplos Decoradores
Uma função pode ter vários decoradores empilhados. A ordem de execução é de baixo para cima:
from functools import wraps
def bold(func):
@wraps(func)
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def italic(func):
@wraps(func)
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@bold
@italic
def hello():
return "Olá"
print(hello()) # <b><i>Olá</i></b>
# Ordem de execução: primeiro @italic, depois @bold
# Equivalente a: hello = bold(italic(hello))
6. Casos de Uso Comuns
- Logging: Registre automaticamente as chamadas e valores de retorno de cada função.
- Temporização de desempenho: Meça o tempo de execução da função para identificar gargalos.
- Verificações de permissão: Frameworks web usam decoradores
@login_requiredpara verificar se usuários estão logados. - Cache:
@functools.lru_cache()armazena em cache automaticamente resultados de funções. - Tentativas: Tente novamente automaticamente um número especificado de vezes em caso de falha de requisição de rede.
❓ Perguntas Frequentes
*args e os argumentos de palavra-chave variáveis (dois asteriscos) são realmente necessários?*args e argumentos de palavra-chave variáveis para passar todos os argumentos. Se estiver envolvendo apenas funções com uma assinatura específica, você pode nomear parâmetros específicos, mas isso é menos genérico. Decoradores genéricos quase sempre usam essa forma de desempacotamento.func passado pela função externa.@property (acesso a atributo), @staticmethod (métodos estáticos), @classmethod (métodos de classe), @functools.lru_cache() (cache), @functools.wraps (preservar info da função), @dataclass (classes de dados). Todos estão na biblioteca padrão.📖 Resumo
- Closure: uma função interna lembra as variáveis da função externa, mesmo após a função externa ter terminado sua execução
- Decorador: uma função que recebe uma função e retorna uma função; usa açúcar sintático
@ @wraps(func)preserva o__name__e__doc__da função original — sempre use em decoradores- Decoradores parametrizados: três níveis de aninhamento — externo recebe parâmetros, médio recebe função, interno lida com lógica
- Múltiplos decoradores são aplicados de baixo para cima
- Decoradores são "funções de funções" — aprimorando funções sem modificar o código original
📝 Exercícios
-
Iniciante (Dificuldade ⭐): Escreva um decorador
print_callque imprime "Chamando função xxx" quando a função executa. -
Intermediário (Dificuldade ⭐⭐): Escreva um decorador
retry(max_attempts=3)que tenta novamente automaticamente o número especificado de vezes quando uma função levanta uma exceção. Se as tentativas se esgotarem, levante a exceção final. Dica: Use um laço for + try-except no wrapper. -
Avançado (Dificuldade ⭐⭐⭐): Escreva um decorador parametrizado
cache_result(ttl=60)que armazena em cache o valor de retorno da função. Dentro dos segundos TTL, chamar com os mesmos parâmetros retorna o resultado em cache; além do TTL, recalcula o valor.