Contents

Decoradores Python: O que são e como usá-los?

Quando se pretende aumentar a funcionalidade de uma função sem alterar explicitamente o seu código-fonte, os decoradores podem revelar-se uma solução eficaz.

Um decorador pode ser descrito como um componente funcional que engloba outra função, proporcionando uma oportunidade de incorporar código adicional antes e depois da função original, resultando num meio conveniente de aumentar as funções pré-existentes com capacidades alargadas.

Como é que os decoradores funcionam?

/pt/images/decorated-gift-boxes.jpg

Uma função Python é um cidadão de primeira classe, o que significa que pode ser atribuída a uma variável, passada como um argumento para outra função ou devolvida de uma função. Esta propriedade permite o desenvolvimento de decoradores em Python.

O conceito de uma função decoradora, que também é referida como uma função envolvente, envolve a criação de outra função com atributos ou métodos adicionados. Esta função secundária, conhecida como decorador, opera como uma espécie de metafunção que melhora a funcionalidade de uma função primária, que é normalmente referida como função de destino. Quando um decorador é aplicado a uma função, o interpretador Python invoca a função decoradora e passa

A função decoradora tem a capacidade de efetuar quaisquer operações desejadas antes e depois de invocar a função de destino. Além disso, pode alterar a funcionalidade da função de destino, devolvendo uma nova função ou substituindo totalmente a função original.

Criando uma Função Decoradora

Para implementar um decorador em Python, deve-se definir uma função que aceite outra função como parâmetro de entrada e retorne uma nova função que envolva a função original, fornecendo também recursos adicionais. Esta função recém-criada é conhecida como a função “wrapper”, e engloba tanto o comportamento da função original como quaisquer capacidades suplementares que foram adicionadas pelo decorador.

 def decorator_func(target_func):
    def wrapper_func():
        # Action before calling target_func
        # ...

        # Call the target function
        target_func()
        
        # Action after calling target_func
        # ...

    return wrapper_func

 

O decorador\_func aceita a target\_func como parâmetro e devolve uma wrapper\_func que executa a target\_func ao mesmo tempo que incorpora funcionalidades adicionais.

Decorando uma Função com o Decorador

Para decorar uma função com um decorador em Python, utilize a notação “@

" e posicione-a imediatamente antes da declaração da função.

 @decorator_func
def my_function():
    # Function implementation
    pass
 

Quando uma função é decorada com outra função, conhecida como decorador, a função original é modificada de tal forma que, quando é chamada, a função decoradora também é executada e passa a função original como um argumento.

Observe que a função do decorador não utiliza parênteses de abertura e fechamento.

O uso do “@

 func = decorator_func(my_function)
 

Manipulação de argumentos de função dentro de decoradores

Ao implementar um decorador que requer a manipulação de argumentos de função, utilize o uso de \*args e **kwargs na função de wrapper. Isto permite que o decorador acomode um número arbitrário de argumentos posicionais e de palavras-chave, respetivamente.

 def decorator_func(target_func):
    def wrapper_func(*args, **kwargs):
        # before calling target_func with args and kwargs
        # ...

        # Call the target function with arguments
        result = target_func(*args, **kwargs)

        # after calling target_func
        # ...
        return result

    return wrapper_func
 

Os parâmetros \*args e **kwargs na função wrapper\_func permitem ao decorador gerir eficazmente quaisquer argumentos passados para a função de destino, independentemente da sua natureza ou complexidade.

Decoradores com argumentos

O fornecimento da capacidade de tratamento de argumentos em decoradores permite uma maior flexibilidade na decoração de funcionalidades.

 def my_decorator(num_times):
    def wrapper(func):
        def inner(*args, **kwargs):
            for _ in range(num_times):
                func(*args, **kwargs)
        return inner
    return wrapper

@my_decorator(2)
def greet(name):
    print(f"Hello, {name}.")
 

A função aceita um único argumento, que é um valor numérico, e retorna um wrapp

 greet("Prince")
 

Imprime “Olá, Príncipe” duas vezes.

Utilizações práticas dos decoradores

Os decoradores são ferramentas versáteis que podem ser utilizadas para uma série de objectivos, incluindo, entre outros, registo, armazenamento em cache, validação de entradas e melhoria do desempenho.

Decorador de registo

Um decorador de registo pode ser utilizado para registar dados relativos ao desempenho de uma função, para efeitos de depuração ou monitorização da atividade dessa função.

 import logging

def my_logger(func):
    def wrapper(*args, **kwargs):
        logging.info("Entering function: %s", func.__name__)
        result = func(*args, **kwargs)
        logging.info("Exiting function: %s", func.__name__)
        return result

    return wrapper
 

Decorador de temporização

Um decorador de temporização pode ser utilizado para quantificar a duração da execução de uma função, o que é benéfico para avaliar e otimizar o código.

 import time

def timing_decorator(target_func):
    def wrapper_func(*args, **kwargs):
        start_time = time.time()
        result = target_func(*args, **kwargs)
        end_time = time.time()
        total = end_time - start_time
        print(f"Function {target_func.__name__} took {total} seconds to run")
        return result

    return wrapper_func
 

Aplicar os decoradores:

 @my_logger
@timing_decorator
def bg_task(n):
    print(f"Background Task, {n}.")
    return "Result !!!"

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    print(bg_task("CPU is Dancing"))
 

A seguinte mensagem será apresentada após a execução da função, juntamente com um relatório da sua duração:

/pt/images/logging-and-timing-output.jpg

Decorador de autorização

Pode utilizar um decorador de autorização para verificar se um utilizador tem as permissões necessárias para aceder a uma função específica. A maioria dos frameworks web usam esse recurso para adicionar uma camada de segurança. Um exemplo comum é o decorador @login_required do Django.

 def authorization_decorator(target_func):
    def wrapper_func(*args, **kwargs):
        if is_user_authorized():
            result = target_func(*args, **kwargs)
            return result
        else:
            raise PermissionError("Unauthorized access")

    return wrapper_func
 

As instâncias acima mencionadas demonstram a versatilidade da utilização de decoradores para diversas aplicações, adaptadas às necessidades e objectivos específicos de cada um.

Conceitos avançados de decoradores

A implementação e utilização de decoradores é um processo simples, mas o seu potencial reside no facto de oferecerem versatilidade e comando adicionais.

Decoradores baseados em classes

Os decoradores baseados em classes em Python oferecem uma abordagem mais refinada e elegante em comparação com os decoradores baseados em funções, permitindo um grau ainda maior de flexibilidade e modularidade no processo de programação.

Um decorador baseado em classe é um padrão de design no qual uma classe implementa a função \_\_call\_\_

 class DecoratorClass:
    def __init__(self, target_func):
        self.target_func = target_func

    def __call__(self, *args, **kwargs):
        # Decorator implementation
        print("Calling function")
        result = self.target_func(*args, **kwargs)
        print("Function called")
        return result
 

As informações fornecidas descrevem uma classe decoradora em Python, que é utilizada para adicionar funcionalidade adicional a uma função existente. O processo de aplicação da funcionalidade do decorador envolve a invocação do método mágico \_\_call\_\_ na função de destino quando esta é decorada com instâncias da classe decoradora

.

A utilização de decoradores baseados em classes permite a preservação do estado em várias invocações através do armazenamento de informações em variáveis de instância, fornecendo assim um meio para implementações de decoradores mais avançadas e versáteis.

Decoradores de aninhamento e encadeamento

O conceito de combinar várias funcionalidades num módulo unificado através da aplicação de vários decoradores numa função solitária pode ser expresso da seguinte forma:

 def decorator1(func):
    def wrapper(*args, **kwargs):
        print("Decorator1...")
        return func(*args, **kwargs)

    return wrapper

def decorator2(func):
    def wrapper(*args, **kwargs):
        print("Decorator2...")
        return func(*args, **kwargs)

    return wrapper

@decorator1
@decorator2
def my_function(n):
    print(n)
 

Ao utilizar a função my\_function, tanto o decorador1 como o decorador2 aplicarão as suas respectivas funcionalidades em sequência. Especificamente, a funcionalidade do decorador1 é executada inicialmente, seguida pela do decorador2.

Um cenário típico envolve a implementação de funcionalidades post-only e login-required para uma view Django.

Os Benefícios dos Decoradores

A utilização de decoradores pode prover uma infinidade de benefícios, tornando sua incorporação imperativa para numerosos empreendimentos Python.

O conceito acima mencionado diz respeito a melhorar a reutilização de código através da segregação de preocupações transversais da lógica fundamental, levando assim a um design de software mais eficiente e modular.

O princípio DRY (Don’t Repeat Yourself) é um conceito fundamental no desenvolvimento de software que enfatiza a importância de evitar a repetição desnecessária de código ou funcionalidade em diferentes partes de um programa. Isto pode ser conseguido através da implementação de uma única função ou módulo que executa a mesma operação ou tarefa que outras funções semelhantes, reduzindo assim a redundância e promovendo a manutenção e escalabilidade do código.

A afirmação acima destaca a importância de garantir que o código-fonte não só é facilmente compreensível, mas também

A passagem acima exibe traços característicos do estilo Pythonic, que dá prioridade a práticas de codificação claras, concisas e elegantes que produzem código legível e de fácil manutenção.

Considere a utilização de decoradores em todo o seu potencial sempre que possível, pois eles oferecem vários benefícios e vantagens.

Melhores práticas para o uso de decoradores

A abordagem ideal para a utilização de decoradores é aderir a um conjunto de diretrizes estabelecidas, que incluem:

A recomendação é atribuir as funcionalidades dos decoradores a uma consideração específica, por exemplo, registo ou armazenamento em cache, a fim de promover a modularidade dentro do código e aumentar a adaptabilidade dos decoradores.

A capacidade de configurar decoradores através da utilização de parâmetros e da eliminação de valores fixos permite adaptar a funcionalidade do decorador quando aplicado a várias funções, aumentando assim a flexibilidade

O objetivo do decorador é acrescentar funcionalidade adicional a uma determinada função, envolvendo-a com outra função, preservando a funcionalidade original. O decorador pode receber um ou mais argumentos como parâmetros, que serão passados para a função envolvida quando esta for chamada. Quando o decorador é aplicado a uma função, a função “envolvida” resultante comporta-se de forma consistente com a função original, mas com os recursos adicionais fornecidos pelo decorador.

É imperativo registar e divulgar meticulosamente quaisquer consequências acessórias que possam resultar da aplicação de um decorador, incluindo, mas não se limitando a, modificações na funcionalidade da função ou a introdução de responsabilidades adicionais, para que os futuros utilizadores possam tomar nota delas para as suas próprias referências.

Pode confiar-se que os decoradores cumprem o objetivo pretendido? Isto pode ser determinado através da implementação de testes unitários que avaliem o seu comportamento em várias condições, incluindo a utilização normal e circunstâncias excepcionais.

Trabalhando com funções em Python

As funções em Python servem como um componente essencial para organizar e redirecionar o código, permitindo o encapsulamento da funcionalidade, a passagem de dados e o retorno dos resultados.

Uma compreensão abrangente do delineamento e utilização de funções é crucial para uma codificação Python proficiente.