Decoradores em Python: Domine essa estratégia

Em Python, decorators (decoradores) são uma ferramenta extremamente poderosa e versátil, permitindo aos programadores modificar o comportamento de funções e métodos de maneira elegante e expressiva. Este artigo oferece um guia prático sobre o que são decoradores, como eles funcionam e como você pode utilizá-los para tornar seu código mais limpo, eficiente e reutilizável.

O Que São Decorators ?

Decorators são, essencialmente, funções que adicionam funcionalidades a outras funções ou métodos sem alterá-los permanentemente. Eles "embrulham" a função original, permitindo executar código antes ou depois dela, e então retornam uma nova função com comportamento modificado. Isso é particularmente útil para aplicar uma lógica comum, como controle de acesso, logging, ou medição de desempenho, a múltiplas funções ou métodos de forma eficaz.

Como Funcionam os Decorators em Python ?

Para entender como os decorators funcionam, é importante compreender que em Python, funções são objetos de primeira classe, o que significa que podem ser passadas e retornadas por outras funções. Um decorator aproveita esse recurso para receber uma função como argumento, processá-la, e retornar uma nova função.

Para seguir com com a leitura desse artigo é necessário um breve entendimento sobre funções em Python, para isso recomendo a leitura do artigo abaixo:

Como criar funções em Python ?
*Texto de Paulo Clemente Desenvolvedor e redator de mídias sociais na Rocketseat Se você está dando os primeiros passos na programação em Python ou deseja aprimorar suas habilidades, entender como criar funções é um passo fundamental. As funções desempenham um papel crucial na construção de programa…

Veja um exemplo simples de um decorador (decorator):

def meu_decorador(funcao):
    def funcao_modificada():
        print("Algo antes da execução da função original.")
        funcao()
        print("Algo após a execução da função original.")
    return funcao_modificada

@meu_decorador
def minha_funcao():
    print("A função original está sendo executada.")

Aqui, @meu_decorador aplica o decorador meu_decorador à função minha_funcao(). Quando minha_funcao() é chamada, o decorador permite executar código antes e depois da execução da função original, enriquecendo seu comportamento sem modificar o corpo original da função.

Aplicando Decoradores em Python

São incrivelmente flexíveis,nos permitindo não apenas modificar o comportamento das funções, mas também aplicar múltiplos decoradores a uma única função. Isso é feito simplesmente empilhando os decoradores acima da definição da função:

@decorador1
@decorador2
def minha_funcao():
    pass

Neste exemplo, decorador2 é aplicado primeiro, seguido por decorador1. A ordem é importante, pois cada decorador modifica o comportamento da função no contexto do que foi aplicado antes dele.

Benefícios dos Decoradores em Python

  • Reusabilidade de Código: Decoradores permitem reutilizar lógica comum a várias funções, mantendo o código DRY (Don't Repeat Yourself).
  • Separação de Preocupações: Eles ajudam a separar as preocupações, mantendo a lógica de negócios separada da lógica de controle ou de monitoramento.
  • Sintaxe Clara e Expressiva: A sintaxe dos decoradores (@) torna o código mais legível e expressivo, facilitando o entendimento de que uma função está sendo modificada ou aprimorada.

Casos de Uso Comuns para Decoradores

Os decoradores em Python são incrivelmente versáteis, encontrando aplicabilidade em uma ampla gama de situações. Aqui estão alguns casos de uso comuns onde os decoradores podem ser particularmente úteis:

Logging

Um dos usos mais comuns de decoradores é adicionar funcionalidades de logging às funções, permitindo que você registre a execução de funções, seus argumentos e valores de retorno sem modificar o corpo da função.

def log_function_data(func):
    def wrapper(*args, **kwargs):
        print(f"Nome da função: {func.__name__}")
        print(f"Argumentos: {args}")
        resultado = func(*args, **kwargs)
        print(f"Resultado: {resultado}")
        return resultado
    return wrapper

@log_function_data
def soma(a, b):
    return a + b

Medição de Desempenho

Decoradores podem ser usados para medir o tempo de execução de uma função, o que é útil para identificar gargalos de desempenho.

import time

def timer(func):
    def wrapper(*args, **kwargs):
        inicio = time.time()
        resultado = func(*args, **kwargs)
        fim = time.time()
        print(f"A função {func.__name__} levou {fim-inicio:.4f} segundos para executar.")
        return resultado
    return wrapper

@timer
def operacao_demorada(n):
    soma = 0
    for i in range(n):
        soma += i
    return soma

Verificação de Autorização

Decoradores podem ser utilizados para verificar se um usuário tem permissão para executar determinada função, o que é bastante comum em aplicações web.

def check_user_permission(user):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if user != 'admin':
                raise Exception("Acesso negado. Usuário não tem permissão.")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@check_user_permission(user="admin")
def delete_database():
    print("Banco de dados deletado com sucesso.")

@check_user_permission(user="guest")
def delete_database():
    print("Banco de dados deletado com sucesso.")

Cache de Resultados

Decoradores são uma excelente maneira de implementar cache, onde os resultados de funções pesadas são armazenados e reutilizados em chamadas subsequentes com os mesmos argumentos.

from functools import lru_cache

@lru_cache(maxsize=32)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

Validação de Argumentos

Você pode usar decoradores para validar os argumentos passados para uma função, garantindo que eles estejam dentro de certos critérios antes de a função ser executada.

def validate_positive(func):
    def wrapper(*args, **kwargs):
        if any(arg < 0 for arg in args if isinstance(arg, (int, float))):
            raise ValueError("Os argumentos devem ser positivos")
        return func(*args, **kwargs)
    return wrapper

@validate_positive
def soma(a, b):
    return a + b

E olha que isso é apenas uma fração do potencial dos decoradores. Eles são uma ferramenta poderosa e flexível no arsenal de qualquer desenvolvedor Python, capaz de tornar o código mais limpo, mais eficiente e mais expressivo.

Tenha acesso aos códigos que usamos como exemplos nesse Notebook

Decoratores são uma ferramenta fantástica em Python, oferecendo uma forma poderosa de adicionar funcionalidades às funções de maneira limpa e reutilizável. Seja para adicionar logs, verificar permissões, ou simplesmente modificar o comportamento de uma função sem alterar seu código original. Com um pouco de prática, você pode começar a tirar proveito dos benefícios que eles oferecem, tornando seu código mais modular, eficiente e fácil de manter.