Executando verificação de segurança...
15

[PYTHON] Usando funções decoradoras (Decorators)

Saudações!

Recentemente estava eu me aventurando com Flask e notei que poderia ser usado um @login_required junto com alguma rota da aplicação e fiquei curioso sobre o funcionamento

Que tal ver como funcionam esses famosos @'s que podemos adicionar uma linha antes da nossas funções em python?

Bora lá!

Antes de tudo a PEP 318 fala muita coisa sobre decoradores para funções e métodos e ainda tem alguns exmplos bem da hora, vale dar uma olhadinha 😉

Em Python, os decoradores são uma forma poderosa de modificar ou estender o comportamento de funções ou métodos sem alterar seu código interno. 🚀🚀

Eles são aplicados usando a sintaxe @decorador acima da definição da sua função, desse jeito.

@meu_decorador
def minha_função ():
  #código

O decorador é, na verdade, uma função que recebe outra função como argumento e retorna uma nova função que geralmente estende ou modifica o comportamento da função original. 🤖

Doido, não?! Relaxa, é muito simples, saca esse exemplo!

# Exemplo de um decorador simples
def meu_decorador(funcao):
    def wrapper():
        print("Antes da execução da função.")
        resultado = funcao()
        print("Depois da execução da função.")
        return resultado
    return wrapper


@meu_decorador
def minha_funcao():
    print("Executando minha função.")


# Chamando a função decorada
minha_funcao()

Execução do código:

Antes da execução da função.
Executando minha função.
Depois da execução da função.

Neste exemplo, @meu_decorador é um decorador que envolve a função minha_funcao com funcionalidades adicionais. Quando chamamos minha_funcao(), o decorador é aplicado automaticamente.

O que acontece nos bastidores é que a função minha_funcao é passada como argumento para a função meu_decorador, e o decorador retorna uma nova função chamada wrapper. Essa nova função inclui o código adicional (como por exemplo imprimir mensagens antes e depois da execução da função original) e, em seguida, chama a função original (funcao()).

É importante notar que, ao usar o decorador com @meu_decorador acima da definição da função, você está essencialmente dizendo que deseja que minha_funcao seja envolvida pelo meu_decorador antes de ser chamada.

UAU! certo? mas e se você precisar passar parâmetros?

Você pode fazer isso utilizando os *args e **kwargs na função wrapper e passá-los na chamada da função que foi decorada, simples assim!

# Exemplo de um decorador simples com parâmetros
def meu_decorador(funcao):
    def wrapper(*args, **kwargs): # <------ Parâmetros para a função
        print("Antes da execução da função.")
        resultado = funcao(*args, **kwargs) # <------ Passagem dos parâmetros na chamada da função
        print("Depois da execução da função.")
        return resultado
    return wrapper


@meu_decorador
def minha_funcao(nome):
    print("Executando minha função.")
    print(f"Olá, {nome}!")

# Chamando a função decorada
minha_funcao("João")

Execução do código:

Antes da execução da função.
Executando minha função.
Olá, João!
Depois da execução da função.

Este é um exemplo básico, mas decoradores podem ter uma variedade de aplicações e serem utilizados para tarefas como verificação de autorização, medição de tempo de execução, entre outros.

O Python possui muitos decoradores incorporados, como @staticmethod, @classmethod, e a biblioteca Flask, assim como outras fazem extenso uso de decoradores para configurar rotas, autenticação, etc.

Exemplos:

TEMPO DE EXECUÇÃO - Mede o tempo de execução da função e faz o log no terminal

import time

def medir_tempo(func):
    def wrapper(*args, **kwargs):
        inicio = time.time()
        resultado = func(*args, **kwargs)
        fim = time.time()
        print(f"{func.__name__} levou {fim - inicio} segundos para executar.")
        return resultado
    return wrapper

@medir_tempo
def operacao_demorada():
    # Código demorado
    time.sleep(2)
    return "Concluído."

FLASK - Acesso de rota da aplicação somente com login efetuado, caso não esteja logado redireciona para a página de login.

from functools import wraps
from flask import g, request, redirect, url_for

# Decorador já existente na biblioteca Flask

df login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if g.user is None:
            return redirect(url_for('login', next=request.url)) #<---- Redireciona para o login
        return f(*args, **kwargs)
    return decorated_function

Criação da rota

@app.route('/secret_page')
@login_required #<---- Exige login para acessar rota secreta
def secret_page():
    pass

Referência: Documentação Flask

Deixa um like se gostou do conteúdo. ❤️

Carregando publicação patrocinada...
2
2
2

Cara tava com uma dificuldade de achar um método agil de colocar um delay no meu jogo em algumas ações e não conseguia pensar em nada,ai lembrei dos decorators quando vi seu post, então dai eu criei decorator que mudou tudo.

Também fiz uma descoberta. Descobri que poosso utilizar uma classe como decorador, que é bem importante para algumas ações, e achei interessante o modo como funciona para utilizar instâncias da classe dentro de um decorador, pois quando utilizamos diretamente na função ele não aceita usarmos a instancia de classe "self", sendo assim a synxtax ficária como a demonstrada abaixo.

import pygame.time


class Timer:
    def __init__(self, delay: int):
        self.last_update = pygame.time.get_ticks()
        self.delay = delay

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            elapsed = pygame.time.get_ticks()
            if elapsed - self.last_update > self.delay:
                self.last_update = elapsed
                return func(*args, **kwargs)

        return wrapper


class Player(Entity):
    def __init__(self, context, image):
        super().__init__(context, image)
        ....
        
        ##########
        # TIMERS #
        ##########

        self.move = Timer(delay=self.speed())(self.move)
        self.frame_motion = Timer(delay=10 if self.speed() >= 500 else 150 * (1 + (self.speed() / 500)))(
            self.frame_motion)
        ....
1

incrível!! Muito bem explicado com uma didatica excepcional! Só que numca entendi muito bem o funcionamento de decorares por esse mesmo motivo não usava, mas agora que entendi o seu real poder vou usar bastante, mas poderia explicar o *args e o **kwargs? nunca entendi o significado.

3

Certamente Tallyson!

Os parâmetros *args e **kwargs são antigos companheiros, sendo eles respectivamente uma lista de valores com tamanho variável e um dicionário (chave - valor) de parâmetros.

*args = []

**kwargs = {}

Sendo assim o *args é utilizado para passar os parâmetros declarados na função.

Exemplo:

# Passando via *args os parâmetros declarados
def exemplo (nome, idade):
    print(f'Olá, {nome}, você tem {idade} anos.')

def args (*args):
    exemplo(*args)

args('João', 99)

Execução

Olá, João, você tem 99 anos.

Neste exemplo foi passada a quantidade de parâmetros exata, se for passado um número de parâmetros inferior ou superior a quantidade esperada, será gerada uma exceção.

O **kwargs é utilizado para passar parâmetros adicionais para a função e da mesma forma a função deve ter declarada o parâmetro **kwargs, para permitir os parâmetros adicionais.

# Passando os parâmetros obrigatórios via *args e mais parâmetros adicionais via **kwargs
def exemplo(nome, idade, **kwargs):
    # Imprime todos os parâmetros adicionais
    for arg, value in kwargs.items():
        print(f'{arg} = {value}')

    print(f'Olá, {nome}, você tem {idade} anos.')

def args(*args, **kwargs):
    exemplo(*args, **kwargs)

args('João', 99, pai = 'Carlos', mae = 'Joana', tio = 'Pedro')

Execução

pai = Carlos
mae = Joana
tio = Pedro
Olá, João, você tem 99 anos.

Overloaded

Os parâmetros adicionais passados via *kwargs são muito utilizados para configurar o comportamento do seu método, por exemplo configurar qual formatação utilizar, qual driver utilizar para calcular e etc.

Espero ter ajudado!

2

Já explicaran o uso dos arga ai

mas um caso de uso interessante que vi no local que trabalho é usar decorators pra implementar retry

A idéia é: caso a função decorada emita um Exception, a mesma será executada mais algumas vezes por meio do controle de um contador recursivo passado como argumento reinjetando.

é curioso

2

Tipo esse retry ???

def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
    """Retry calling the decorated function using an exponential backoff.

    http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
    original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry

    :param ExceptionToCheck: the exception to check. may be a tuple of
        exceptions to check
    :type ExceptionToCheck: Exception or tuple
    :param tries: number of times to try (not retry) before giving up
    :type tries: int
    :param delay: initial delay between retries in seconds
    :type delay: int
    :param backoff: backoff multiplier e.g. value of 2 will double the delay
        each retry
    :type backoff: int
    :param logger: logger to use. If None, print
    :type logger: logging.Logger instance
    """

    def deco_retry(f):

        @wraps(f)
        def f_retry(*args, **kwargs):
            mtries, mdelay = tries, delay
            while mtries > 1:
                try:
                    return f(*args, **kwargs)
                except ExceptionToCheck as e:
                    msg = "%s, Retrying in %d seconds..." % (str(e), mdelay)
                    if logger:
                        logger.warning(msg)
                    else:
                        print(msg)
                    time.sleep(mdelay)
                    mtries -= 1
                    mdelay *= backoff
            return f(*args, **kwargs)

        return f_retry

    return deco_retry
0
0