O que são funções decoradoras em Python?
As funções decoradoras em Python são um recurso poderoso que permite modificar o comportamento de uma função ou método. Elas são frequentemente utilizadas para adicionar funcionalidades a funções existentes de maneira limpa e legível, sem a necessidade de alterar o código da função original. Em essência, uma função decoradora é uma função que recebe outra função como argumento e retorna uma nova função que geralmente estende ou altera o comportamento da função original. Essa técnica é amplamente utilizada em frameworks web, como Flask e Django, para adicionar funcionalidades como autenticação, logging e controle de acesso.
Como funcionam as funções decoradoras?
As funções decoradoras funcionam através do conceito de “higher-order functions”, que são funções que podem receber outras funções como argumentos ou retornar funções. Quando uma função decoradora é aplicada a uma função, ela envolve a função original, permitindo que você execute código antes ou depois da execução da função original. Para aplicar uma decoradora, você utiliza a sintaxe do símbolo “@” seguido pelo nome da decoradora acima da definição da função que deseja decorar. Essa abordagem torna o código mais legível e modular, facilitando a manutenção e a reutilização.
Exemplo básico de uma função decoradora
Para ilustrar como criar funções decoradoras em Python, vamos considerar um exemplo simples. Suponha que você tenha uma função que imprime uma mensagem e deseja adicionar um comportamento que exiba a hora em que a função foi chamada. Você pode criar uma decoradora chamada `decorador_hora` que adiciona essa funcionalidade. O código seria algo assim:
“`python
import datetime
def decorador_hora(func):
def wrapper(*args, **kwargs):
print(f”A função foi chamada em: {datetime.datetime.now()}”)
return func(*args, **kwargs)
return wrapper
@decorador_hora
def minha_funcao():
print(“Olá, mundo!”)
minha_funcao()
“`
Neste exemplo, a função `decorador_hora` recebe a função `minha_funcao` como argumento e a envolve em uma nova função chamada `wrapper`, que imprime a hora antes de chamar a função original.
Passando argumentos para funções decoradoras
Em alguns casos, você pode querer que sua decoradora aceite argumentos. Para isso, você precisa criar uma função decoradora que retorna outra função decoradora. Isso pode ser feito da seguinte maneira:
“`python
def decorador_com_parametro(param):
def decorador(func):
def wrapper(*args, **kwargs):
print(f”Parâmetro recebido: {param}”)
return func(*args, **kwargs)
return wrapper
return decorador
@decorador_com_parametro(“Teste”)
def minha_funcao():
print(“Olá, mundo!”)
minha_funcao()
“`
Neste exemplo, a decoradora `decorador_com_parametro` aceita um argumento `param`, que é impresso antes da execução da função original. Essa abordagem permite uma flexibilidade maior ao criar decoradoras personalizadas.
Utilizando múltiplas decoradoras
Python permite que você aplique múltiplas decoradoras a uma única função. Isso é feito simplesmente empilhando as decoradoras uma sobre a outra. Por exemplo:
“`python
@decorador_hora
@decorador_com_parametro(“Teste”)
def minha_funcao():
print(“Olá, mundo!”)
minha_funcao()
“`
Neste caso, a função `minha_funcao` será decorada primeiro pela `decorador_com_parametro`, e em seguida pela `decorador_hora`. A ordem das decoradoras é importante, pois a função mais interna é executada primeiro, seguida pela externa.
Decoradores embutidos em Python
Python já vem com alguns decoradores embutidos que são amplamente utilizados. Um exemplo é o `@staticmethod`, que transforma um método em um método estático, permitindo que ele seja chamado na classe sem a necessidade de uma instância. Outro exemplo é o `@classmethod`, que permite que um método acesse a classe em vez de uma instância específica. Esses decoradores são úteis para organizar e estruturar melhor o código, especialmente em classes.
Decoradores para controle de acesso
Um uso comum de decoradores é o controle de acesso em aplicações web. Por exemplo, você pode criar uma decoradora que verifica se um usuário está autenticado antes de permitir o acesso a uma determinada função. Isso pode ser feito da seguinte maneira:
“`python
def requer_autenticacao(func):
def wrapper(usuario):
if usuario.is_authenticated:
return func(usuario)
else:
print(“Acesso negado. Usuário não autenticado.”)
return wrapper
@requer_autenticacao
def funcao_protegida(usuario):
print(“Bem-vindo à função protegida!”)
# Exemplo de uso
class Usuario:
def __init__(self, autenticado):
self.is_authenticated = autenticado
usuario = Usuario(autenticado=True)
funcao_protegida(usuario)
“`
Neste exemplo, a decoradora `requer_autenticacao` verifica se o usuário está autenticado antes de permitir o acesso à função `funcao_protegida`.
Desempenho e otimização com decoradores
Além de adicionar funcionalidades, os decoradores também podem ser utilizados para otimizar o desempenho de funções. Por exemplo, você pode criar uma decoradora que implementa caching, armazenando os resultados de chamadas de função para evitar cálculos repetidos. Isso é especialmente útil em funções que realizam operações pesadas ou que dependem de chamadas a APIs externas. Um exemplo simples de caching pode ser implementado da seguinte forma:
“`python
def cache(func):
resultados = {}
def wrapper(*args):
if args not in resultados:
resultados[args] = func(*args)
return resultados[args]
return wrapper
@cache
def calcular_fatorial(n):
if n == 0:
return 1
else:
return n * calcular_fatorial(n – 1)
print(calcular_fatorial(5))
print(calcular_fatorial(5)) # Chamada em cache
“`
Neste exemplo, a decoradora `cache` armazena os resultados das chamadas da função `calcular_fatorial`, melhorando a eficiência em chamadas subsequentes com os mesmos argumentos.