Decoradores
Introdução
Decoradores são uma poderosa ferramenta em Python que permite modificar ou estender o comportamento de funções e classes de forma limpa e reutilizável. Eles seguem o padrão de design Decorator, que permite adicionar funcionalidades a objetos sem modificar sua estrutura.
Objetivos de Aprendizado
- Entender o conceito de decoradores e sua importância em Python
- Aprender a criar e aplicar decoradores básicos em funções
- Compreender como funcionam decoradores com argumentos
- Explorar decoradores de classe e métodos
- Conhecer decoradores comuns na biblioteca padrão e frameworks
- Aplicar decoradores para resolver problemas práticos
Conceitos Básicos de Decoradores
Um decorador é uma função que recebe outra função como entrada e retorna uma terceira função, geralmente estendendo a funcionalidade da função original sem modificá-la diretamente.
# Decoradores são funções que envolvem outras funções
# Eles permitem modificar o comportamento de funções sem alterá-las
# Função que será decorada
def saudacao(nome):
return f"Olá, {nome}!"
# Função decoradora
def fazer_educado(funcao):
# Esta função interna é o "wrapper" (envoltório)
def wrapper(nome):
# Adiciona "por favor" e "obrigado"
print("Por favor...")
resultado = funcao(nome)
print("Obrigado!")
return resultado
# Retorna o wrapper, não o resultado da função
return wrapper
# Aplicando o decorador manualmente
saudacao_educada = fazer_educado(saudacao)
# Chamando a função decorada
print(saudacao_educada("Maria"))
# Saída:
# Por favor...
# Obrigado!
# Olá, Maria!
# A sintaxe @ é um açúcar sintático para aplicar decoradores
# Definição do decorador
def fazer_educado(funcao):
def wrapper(nome):
print("Por favor...")
resultado = funcao(nome)
print("Obrigado!")
return resultado
return wrapper
# Aplicando o decorador com a sintaxe @
@fazer_educado
def saudacao(nome):
return f"Olá, {nome}!"
# Quando usamos @fazer_educado, é o mesmo que:
# saudacao = fazer_educado(saudacao)
# Chamando a função decorada
print(saudacao("João"))
# Saída:
# Por favor...
# Obrigado!
# Olá, João!
# A magia dos decoradores está na avaliação em tempo de definição
def meu_decorador(funcao):
print(f"Decorando {funcao.__name__}")
def wrapper(*args, **kwargs):
print(f"Iniciando {funcao.__name__}")
resultado = funcao(*args, **kwargs)
print(f"Finalizando {funcao.__name__}")
return resultado
print("Decorador pronto!")
return wrapper
print("Definindo função...")
@meu_decorador
def minha_funcao(x, y):
print(f"Executando com {x} e {y}")
return x + y
print("Função definida!")
resultado = minha_funcao(5, 3)
print(f"Resultado: {resultado}")
# Saída:
# Definindo função...
# Decorando minha_funcao
# Decorador pronto!
# Função definida!
# Iniciando minha_funcao
# Executando com 5 e 3
# Finalizando minha_funcao
# Resultado: 8
Decoradores com functools.wraps
Quando criamos decoradores, a função original perde seus metadados (nome, docstring, etc.). O functools.wraps
resolve esse problema.
def meu_decorador(funcao):
def wrapper(*args, **kwargs):
"""Função wrapper interna"""
print("Antes")
resultado = funcao(*args, **kwargs)
print("Depois")
return resultado
return wrapper
@meu_decorador
def soma(a, b):
"""Soma dois números."""
return a + b
# Verificando o nome e docstring da função decorada
print(f"Nome: {soma.__name__}") # Nome: wrapper
print(f"Docstring: {soma.__doc__}") # Docstring: Função wrapper interna
# Isso é um problema quando usamos ferramentas de documentação
# ou depuração que dependem desses metadados
from functools import wraps
def meu_decorador(funcao):
@wraps(funcao) # Preserva os metadados da função original
def wrapper(*args, **kwargs):
"""Função wrapper interna"""
print("Antes")
resultado = funcao(*args, **kwargs)
print("Depois")
return resultado
return wrapper
@meu_decorador
def soma(a, b):
"""Soma dois números."""
return a + b
# Agora os metadados são preservados
print(f"Nome: {soma.__name__}") # Nome: soma
print(f"Docstring: {soma.__doc__}") # Docstring: Soma dois números.
Prática Recomendada
Sempre use @functools.wraps
ao criar decoradores para preservar os metadados da função original. Isso é importante para depuração, documentação e introspection do código.
Decoradores com Argumentos
Decoradores também podem aceitar argumentos, o que aumenta sua flexibilidade.
from functools import wraps
def repetir(vezes=1):
"""Decorador que repete a execução da função decorada."""
# Esta função externa captura os argumentos do decorador
def decorador(funcao):
# Esta função intermediária é o decorador real
@wraps(funcao)
def wrapper(*args, **kwargs):
# Esta função interna envolve a função original
resultado = None
for _ in range(vezes):
resultado = funcao(*args, **kwargs)
return resultado
return wrapper
return decorador
# Usando o decorador com argumentos
@repetir(vezes=3)
def saudacao(nome):
print(f"Olá, {nome}!")
return nome
# Chamando a função decorada
saudacao("Ana")
# Saída:
# Olá, Ana!
# Olá, Ana!
# Olá, Ana!
from functools import wraps
def registrar(prefixo="INFO"):
"""Decorador que registra informações sobre a chamada da função."""
def decorador(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
print(f"[{prefixo}] Chamando {funcao.__name__}")
argumentos = [str(arg) for arg in args]
kwargs_str = [f"{k}={v}" for k, v in kwargs.items()]
todos_args = argumentos + kwargs_str
print(f"[{prefixo}] Argumentos: {', '.join(todos_args)}")
resultado = funcao(*args, **kwargs)
print(f"[{prefixo}] {funcao.__name__} retornou: {resultado}")
return resultado
return wrapper
return decorador
# Usando com valor padrão
@registrar() # Observe os parênteses vazios
def soma(a, b):
return a + b
# Usando com argumento personalizado
@registrar(prefixo="DEBUG")
def multiplica(a, b):
return a * b
soma(5, 3)
# [INFO] Chamando soma
# [INFO] Argumentos: 5, 3
# [INFO] soma retornou: 8
multiplica(4, 2)
# [DEBUG] Chamando multiplica
# [DEBUG] Argumentos: 4, 2
# [DEBUG] multiplica retornou: 8
from functools import wraps
import time
def cronometrar(funcao=None, *, decimal_places=4):
"""
Decorador que mede o tempo de execução de uma função.
Pode ser usado com ou sem argumentos.
"""
# Se chamado sem argumentos como @cronometrar
if funcao is not None:
@wraps(funcao)
def wrapper_simples(*args, **kwargs):
inicio = time.time()
resultado = funcao(*args, **kwargs)
fim = time.time()
print(f"{funcao.__name__} levou {fim - inicio:.4f} segundos")
return resultado
return wrapper_simples
# Se chamado com argumentos como @cronometrar(decimal_places=2)
else:
def decorador(func):
@wraps(func)
def wrapper(*args, **kwargs):
inicio = time.time()
resultado = func(*args, **kwargs)
fim = time.time()
print(f"{func.__name__} levou {fim - inicio:.{decimal_places}f} segundos")
return resultado
return wrapper
return decorador
# Uso sem argumentos
@cronometrar
def operacao_lenta():
time.sleep(0.5)
# Uso com argumentos
@cronometrar(decimal_places=2)
def outra_operacao():
time.sleep(0.3)
operacao_lenta() # operacao_lenta levou 0.5001 segundos
outra_operacao() # outra_operacao levou 0.30 segundos
Decoradores Encadeados
É possível aplicar vários decoradores a uma mesma função, cada um adicionando sua própria funcionalidade.
from functools import wraps
# Decorador para registrar a chamada
def registrar(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
print(f"Chamando {funcao.__name__}")
return funcao(*args, **kwargs)
return wrapper
# Decorador para verificar argumentos
def validar_positivos(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
for arg in args:
if isinstance(arg, (int, float)) e arg < 0:
raise ValueError("Argumento negativo não permitido")
return funcao(*args, **kwargs)
return wrapper
# Aplicando múltiplos decoradores
@registrar
@validar_positivos
def calcular_raiz_quadrada(numero):
import math
return math.sqrt(numero)
# A ordem é importante! É aplicada de baixo para cima
# Primeiro validar_positivos, depois registrar
try:
resultado = calcular_raiz_quadrada(16)
print(f"Resultado: {resultado}")
resultado = calcular_raiz_quadrada(-4)
print(f"Resultado: {resultado}")
except ValueError as e:
print(f"Erro: {e}")
# Saída:
# Chamando calcular_raiz_quadrada
# Resultado: 4.0
# Chamando calcular_raiz_quadrada
# Erro: Argumento negativo não permitido
def decorador1(funcao):
def wrapper(*args, **kwargs):
print("Decorador 1 - Início")
resultado = funcao(*args, **kwargs)
print("Decorador 1 - Fim")
return resultado
return wrapper
def decorador2(funcao):
def wrapper(*args, **kwargs):
print("Decorador 2 - Início")
resultado = funcao(*args, **kwargs)
print("Decorador 2 - Fim")
return resultado
return wrapper
def decorador3(funcao):
def wrapper(*args, **kwargs):
print("Decorador 3 - Início")
resultado = funcao(*args, **kwargs)
print("Decorador 3 - Fim")
return resultado
return wrapper
# Os decoradores são aplicados de baixo para cima
@decorador1
@decorador2
@decorador3
def minha_funcao():
print("Executando a função principal")
minha_funcao()
# Saída:
# Decorador 1 - Início
# Decorador 2 - Início
# Decorador 3 - Início
# Executando a função principal
# Decorador 3 - Fim
# Decorador 2 - Fim
# Decorador 1 - Fim
Ordem dos Decoradores
A ordem em que os decoradores são aplicados é importante. Os decoradores mais próximos da função são aplicados primeiro, e depois os mais externos. É como vestir camadas de roupas: a primeira camada fica mais próxima do corpo.
Decoradores de Classe
Os decoradores não estão limitados a funções; eles também podem ser aplicados a classes.
def adicionar_saudacao(classe):
"""Decorador que adiciona um método de saudação à classe."""
def saudacao(self, nome):
return f"{self.__class__.__name__} diz: Olá, {nome}!"
# Adicionando o método à classe
classe.saudacao = saudacao
return classe
# Aplicando o decorador à classe
@adicionar_saudacao
class Pessoa:
def __init__(self, nome):
self.nome = nome
def apresentar(self):
return f"Eu sou {self.nome}"
# Usando a classe decorada
pessoa = Pessoa("Carlos")
print(pessoa.apresentar()) # Eu sou Carlos
print(pessoa.saudacao("Maria")) # Pessoa diz: Olá, Maria!
class Contador:
"""Uma classe que serve como decorador para contar chamadas de função."""
def __init__(self, funcao):
self.funcao = funcao
self.contagem = 0
# Copiando os metadados da função original
self.__name__ = funcao.__name__
self.__doc__ = funcao.__doc__
def __call__(self, *args, **kwargs):
"""Método chamado quando o objeto é usado como uma função."""
self.contagem += 1
print(f"{self.funcao.__name__} foi chamada {self.contagem} vezes")
return self.funcao(*args, **kwargs)
# Usando a classe como decorador
@Contador
def minha_funcao(x, y):
"""Uma função simples que soma dois números."""
return x + y
# Cada vez que chamamos a função, o contador é incrementado
print(minha_funcao(1, 2)) # minha_funcao foi chamada 1 vezes, 3
print(minha_funcao(3, 4)) # minha_funcao foi chamada 2 vezes, 7
print(minha_funcao(5, 6)) # minha_funcao foi chamada 3 vezes, 11
from functools import wraps
def registrar_metodo(metodo):
"""Decorador para registrar chamadas de métodos."""
@wraps(metodo)
def wrapper(self, *args, **kwargs):
print(f"Chamando {metodo.__name__} para {self.__class__.__name__}")
return metodo(self, *args, **kwargs)
return wrapper
class Calculadora:
@registrar_metodo
def somar(self, a, b):
return a + b
@registrar_metodo
def multiplicar(self, a, b):
return a * b
# Usando os métodos decorados
calc = Calculadora()
print(calc.somar(5, 3)) # Chamando somar para Calculadora, 8
print(calc.multiplicar(4, 2)) # Chamando multiplicar para Calculadora, 8
Decoradores Especiais
Python tem alguns decoradores especiais integrados e padrões comuns para situações específicas.
class Matematica:
valor = 10
def __init__(self, x):
self.x = x
# Método de instância (acessa self)
def dobrar(self):
return self.x * 2
# Método de classe (acessa a classe, não a instância)
@classmethod
def triplo_valor(cls):
return cls.valor * 3
# Método estático (não acessa nem a classe nem a instância)
@staticmethod
def quadrado(y):
return y * y
# Usando métodos de instância
mat = Matematica(5)
print(mat.dobrar()) # 10 (dobro de 5)
# Usando método de classe
print(Matematica.triplo_valor()) # 30 (triplo de 10)
# Usando método estático
print(Matematica.quadrado(4)) # 16 (quadrado de 4)
print(mat.quadrado(4)) # 16 (também pode ser chamado pela instância)
class Temperatura:
def __init__(self, celsius=0):
self._celsius = celsius
# Getter
@property
def celsius(self):
return self._celsius
# Setter
@celsius.setter
def celsius(self, valor):
if valor < -273.15:
raise ValueError("Temperatura abaixo do zero absoluto!")
self._celsius = valor
# Propriedade calculada
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, valor):
self.celsius = (valor - 32) * 5/9
@property
def kelvin(self):
return self._celsius + 273.15
@kelvin.setter
def kelvin(self, valor):
self.celsius = valor - 273.15
# Usando as propriedades
temp = Temperatura(25)
print(f"Celsius: {temp.celsius}") # Celsius: 25
print(f"Fahrenheit: {temp.fahrenheit}") # Fahrenheit: 77.0
print(f"Kelvin: {temp.kelvin}") # Kelvin: 298.15
# Alterando temperatura
temp.fahrenheit = 68
print(f"Celsius: {temp.celsius}") # Celsius: 20.0
try:
temp.celsius = -300 # Abaixo do zero absoluto
except ValueError as e:
print(f"Erro: {e}") # Erro: Temperatura abaixo do zero absoluto!
import time
from functools import lru_cache
# Função sem cache
def fibonacci_sem_cache(n):
if n <= 1:
return n
return fibonacci_sem_cache(n-1) + fibonacci_sem_cache(n-2)
# Função com cache
@lru_cache(maxsize=None) # Cache ilimitado
def fibonacci_com_cache(n):
if n <= 1:
return n
return fibonacci_com_cache(n-1) + fibonacci_com_cache(n-2)
# Comparando desempenho
def testar_tempo(func, arg):
inicio = time.time()
resultado = func(arg)
fim = time.time()
print(f"{func.__name__}({arg}) = {resultado}, tempo: {fim - inicio:.6f} segundos")
# Testando funções
n = 30
testar_tempo(fibonacci_sem_cache, n) # Muito lento!
testar_tempo(fibonacci_com_cache, n) # Rápido!
# fibonacci_sem_cache(30) = 832040, tempo: 0.308167 segundos
# fibonacci_com_cache(30) = 832040, tempo: 0.000026 segundos
Casos de Uso Comuns
from functools import wraps
def validar_tipos(*tipos):
"""Verifica se os argumentos são dos tipos esperados."""
def decorador(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
# Verificando os argumentos posicionais
for i, (arg, tipo) in enumerate(zip(args, tipos)):
if not isinstance(arg, tipo):
raise TypeError(
f"Argumento {i+1} deve ser {tipo.__name__}, "
f"mas recebeu {type(arg).__name__}"
)
return funcao(*args, **kwargs)
return wrapper
return decorador
@validar_tipos(int, int)
def soma(a, b):
return a + b
@validar_tipos(str, int)
def repetir(texto, vezes):
return texto * vezes
# Chamadas válidas
print(soma(5, 3)) # 8
print(repetir("abc", 3)) # abcabcabc
# Chamadas inválidas
try:
soma("5", 3)
except TypeError as e:
print(f"Erro: {e}") # Erro: Argumento 1 deve ser int, mas recebeu str
try:
repetir(5, 3)
except TypeError as e:
print(f"Erro: {e}") # Erro: Argumento 1 deve ser str, mas recebeu int
import time
import logging
from functools import wraps
# Configuração básica de logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def log_tempo(funcao):
"""Registra o tempo de execução de uma função."""
@wraps(funcao)
def wrapper(*args, **kwargs):
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
assinatura = ", ".join(args_repr + kwargs_repr)
logging.info(f"Chamando {funcao.__name__}({assinatura})")
inicio = time.time()
try:
resultado = funcao(*args, **kwargs)
return resultado
finally:
fim = time.time()
tempo_execucao = fim - inicio
logging.info(f"{funcao.__name__} levou {tempo_execucao:.4f} segundos para executar")
return wrapper
@log_tempo
def operacao_pesada(tamanho, repeticoes=1):
"""Função que simula uma operação pesada."""
resultado = 0
for _ in range(repeticoes):
for i in range(tamanho):
resultado += i
time.sleep(0.01) # Simula processamento
return resultado
# Chamando a função decorada
resultado = operacao_pesada(10000, repeticoes=2)
print(f"Resultado: {resultado}")
# Logs gerados:
# 2023-05-10 14:30:45,123 - INFO - Chamando operacao_pesada(10000, repeticoes=2)
# 2023-05-10 14:30:45,369 - INFO - operacao_pesada levou 0.2461 segundos para executar
import time
import random
from functools import wraps
def retry(tentativas=3, delay_inicial=1, fator_backoff=2):
"""
Decorador que tenta executar a função várias vezes com backoff exponencial.
Args:
tentativas: Número máximo de tentativas
delay_inicial: Tempo inicial de espera (em segundos)
fator_backoff: Multiplicador para aumentar o tempo de espera
"""
def decorador(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
delay = delay_inicial
ultima_excecao = None
for tentativa in range(1, tentativas + 1):
try:
return funcao(*args, **kwargs)
except Exception as e:
print(f"Tentativa {tentativa}/{tentativas} falhou: {str(e)}")
ultima_excecao = e
if tentativa < tentativas:
tempo_espera = delay * (0.5 + random.random()) # Jitter
print(f"Esperando {tempo_espera:.2f} segundos antes da próxima tentativa")
time.sleep(tempo_espera)
delay *= fator_backoff
# Se chegou aqui, todas as tentativas falharam
raise ultima_excecao
return wrapper
return decorador
# Simulando uma função instável
@retry(tentativas=4, delay_inicial=0.5, fator_backoff=2)
def operacao_instavel(chance_falha=0.7):
"""Simula uma operação que falha aleatoriamente."""
if random.random() < chance_falha:
raise ConnectionError("Falha na conexão")
return "Operação bem-sucedida"
# Testando a função
try:
resultado = operacao_instavel()
print(resultado)
except ConnectionError as e:
print(f"Falha final: {e}")
from functools import wraps
def injetar(**dependencias):
"""
Injeta dependências como argumentos nomeados em uma função.
Exemplo:
@injetar(db=obter_conexao_db, logger=obter_logger)
def minha_funcao(x, y, db=None, logger=None):
...
"""
def decorador(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs):
# Para cada dependência especificada
for nome, provedor in dependencias.items():
# Se o argumento não foi fornecido explicitamente
if nome not in kwargs:
# Injeta o valor resolvido da dependência
kwargs[nome] = provedor() if callable(provedor) else provedor
return funcao(*args, **kwargs)
return wrapper
return decorador
# Simulando provedores de dependência
def obter_logger():
class Logger:
def info(self, msg): print(f"INFO: {msg}")
def error(self, msg): print(f"ERROR: {msg}")
return Logger()
def obter_config():
return {"timeout": 30, "retry": True}
# Usando o decorador
@injetar(logger=obter_logger, config=obter_config)
def processar_dados(dados, logger=None, config=None):
"""Processa dados usando as dependências injetadas."""
logger.info(f"Processando {len(dados)} itens")
timeout = config.get("timeout", 10)
logger.info(f"Timeout configurado: {timeout}")
# ... lógica de processamento ...
return len(dados)
# Chamando a função sem fornecer as dependências
resultado = processar_dados([1, 2, 3, 4, 5])
print(f"Resultado: {resultado}")
# Chamando e substituindo uma dependência
class MeuLogger:
def info(self, msg): print(f">> {msg}")
def error(self, msg): print(f"!! {msg}")
resultado = processar_dados([1, 2, 3], logger=MeuLogger())
Decoradores em Frameworks e Bibliotecas
Os decoradores são amplamente utilizados em frameworks e bibliotecas Python.
# Em Flask, decoradores são usados para definir rotas
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return 'Página inicial'
@app.route('/usuario/<nome>')
def perfil(nome):
return f'Perfil de {nome}'
@app.route('/api/dados', methods=['GET', 'POST'])
def api_dados():
return {'status': 'sucesso'}
if __name__ == '__main__':
app.run(debug=True)
# Em Django, decoradores são usados para muitas coisas
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
# Requer autenticação
@login_required
def perfil(request):
return render(request, 'perfil.html')
# Restringe métodos HTTP
@require_http_methods(["GET", "POST"])
def contato(request):
if request.method == "POST":
# Processa o formulário
return render(request, 'obrigado.html')
return render(request, 'contato.html')
# Pytest usa decoradores para marcar testes
import pytest
# Marca este teste para pular
@pytest.mark.skip(reason="Funcionalidade ainda não implementada")
def test_futura_funcionalidade():
assert False
# Marca que este teste deve falhar
@pytest.mark.xfail
def test_bug_conhecido():
assert 1 == 2
# Parametrização de testes
@pytest.mark.parametrize("entrada,esperado", [
(1, 1),
(2, 4),
(3, 9),
(4, 16),
])
def test_quadrado(entrada, esperado):
assert entrada ** 2 == esperado
# Click usa decoradores para criar interfaces de linha de comando
import click
@click.group()
def cli():
"""Ferramenta de linha de comando de exemplo."""
pass
@cli.command()
@click.argument('nome')
@click.option('--saudacao', '-s', default='Olá')
def cumprimentar(nome, saudacao):
"""Cumprimenta alguém."""
click.echo(f"{saudacao}, {nome}!")
@cli.command()
@click.argument('x', type=int)
@click.argument('y', type=int)
def somar(x, y):
"""Soma dois números."""
resultado = x + y
click.echo(f"{x} + {y} = {resultado}")
if __name__ == '__main__':
cli()
Boas Práticas com Decoradores
# Boas práticas para decoradores
# 1. Sempre use functools.wraps
from functools import wraps
def meu_decorador(funcao):
@wraps(funcao) # Preserva os metadados
def wrapper(*args, **kwargs):
return funcao(*args, **kwargs)
return wrapper
# 2. Aceite *args e **kwargs para maior flexibilidade
def decorador_flexivel(funcao):
@wraps(funcao)
def wrapper(*args, **kwargs): # Aceita qualquer argumento
return funcao(*args, **kwargs)
return wrapper
# 3. Faça decoradores combinados para evitar repetição
def combinar_decoradores(*decoradores):
def decorador_combinado(funcao):
for decorador in reversed(decoradores): # Aplica na ordem correta
funcao = decorador(funcao)
return funcao
return decorador_combinado
# Uso:
@combinar_decoradores(decorador1, decorador2, decorador3)
def minha_funcao():
pass
from functools import wraps
def decorador_seguro(funcao):
"""Decorador que captura exceções e fornece feedback útil."""
@wraps(funcao)
def wrapper(*args, **kwargs):
try:
return funcao(*args, **kwargs)
except Exception as e:
print(f"Erro ao executar {funcao.__name__}: {e}")
# Decide se relança a exceção ou retorna um valor padrão
# Aqui decidimos relançar
raise
return wrapper
@decorador_seguro
def divisao(a, b):
return a / b
# Teste
try:
resultado = divisao(10, 0)
except ZeroDivisionError:
print("Capturei a exceção relançada")
def registrar(funcao):
"""
Decorador que registra chamadas de função.
Args:
funcao: A função a ser decorada.
Returns:
Uma função wrapper que registra a chamada e executa a função original.
Exemplo:
@registrar
def minha_funcao(x, y):
return x + y
"""
@wraps(funcao)
def wrapper(*args, **kwargs):
"""
Wrapper que registra a chamada da função decorada.
Args:
*args: Argumentos posicionais para a função original.
**kwargs: Argumentos nomeados para a função original.
Returns:
O resultado da função original.
"""
print(f"Chamando {funcao.__name__} com {args} e {kwargs}")
return funcao(*args, **kwargs)
return wrapper
# A documentação tanto do decorador quanto do wrapper fica disponível
help(registrar)
@registrar
def soma(a, b):
"""Soma dois números."""
return a + b
# A documentação original da função é preservada graças ao @wraps
help(soma)
Resumo
Nesta aula, você aprendeu sobre:
- Conceitos básicos de decoradores em Python
- Como criar e aplicar decoradores em funções
- A importância do functools.wraps para preservar metadados
- Como implementar decoradores com argumentos
- Encadeamento de múltiplos decoradores
- Decoradores de classe e classes como decoradores
- Decoradores especiais como
@property
,@classmethod
e@lru_cache
- Casos de uso comuns para decoradores
- Como decoradores são usados em frameworks populares
- Boas práticas para criar e usar decoradores
Recursos de aprendizado
Próximos Passos
Na próxima aula, exploraremos Programação Orientada a Objetos em Python, um paradigma fundamental para organizar código em aplicações complexas.