Explicado (com exemplos e casos de uso)

Decoradores Python são uma construção incrivelmente útil em Python. Usando decoradores em Python, podemos modificar o comportamento de uma função envolvendo-a dentro de outra função. Os decoradores nos permitem escrever um código mais limpo e compartilhar funcionalidades. Este artigo é um tutorial não apenas sobre como usar decoradores, mas como criá-los.

Conhecimento de pré-requisito

O tópico de decoradores em Python requer algum conhecimento prévio. Abaixo, listei alguns conceitos com os quais você já deve estar familiarizado para entender este tutorial. Também vinculei recursos onde você pode revisar os conceitos, se necessário.

Python básico

Este tópico é um tópico mais intermediário/avançado. Como resultado, antes de tentar aprender, você já deve estar familiarizado com os fundamentos do Python, como tipos de dados, funções, objetos e classes.

Você também deve entender alguns conceitos orientados a objetos, como getters, setters e construtores. Se você não estiver familiarizado com a linguagem de programação Python, aqui estão alguns recursos para você começar.

Funções são cidadãos de primeira classe

Além do Python básico, você também deve estar ciente desse conceito mais avançado do Python. Funções, e praticamente tudo em Python, são objetos como int ou string. Por serem objetos, você pode fazer algumas coisas com eles, a saber:

  • Você pode passar uma função como argumento para outra função da mesma forma que passa uma string ou int como argumento de função.
  • As funções também podem ser retornadas por outras funções, como você retornaria outra string ou valores int.
  • Funções podem ser armazenadas em variáveis

Na verdade, a única diferença entre objetos funcionais e outros objetos é que os objetos funcionais contêm o método mágico __call__().

Esperamos que, neste ponto, você esteja confortável com o conhecimento de pré-requisito. Podemos começar a discutir o tópico principal.

O que é um decorador Python?

Um decorador Python é simplesmente uma função que recebe uma função como argumento e retorna uma versão modificada da função que foi passada. Em outras palavras, a função foo é um decorador se receber, como argumento, a barra de funções e retorna outra função baz.

A função baz é uma modificação de bar no sentido de que dentro do corpo de baz há uma chamada para a função bar. No entanto, antes e depois da chamada para bar, baz pode fazer qualquer coisa. Isso foi um bocado; aqui está um código para ilustrar a situação:

# Foo is a decorator, it takes in another function, bar as an argument
def foo(bar):

    # Here we create baz, a modified version of bar
    # baz will call bar but can do anything before and after the function call
    def baz():

        # Before calling bar, we print something
        print("Something")

        # Then we run bar by making a function call
        bar()

        # Then we print something else after running bar
        print("Something else")

    # Lastly, foo returns baz, a modified version of bar
    return baz

Como criar um decorador em Python?

Para ilustrar como os decorators são criados e usados ​​em Python, vou ilustrar isso com um exemplo simples. Neste exemplo, criaremos uma função logger decorador que registrará o nome da função que está decorando toda vez que essa função for executada.

  9 melhores jogos da máfia com as melhores missões e missões secundárias

Para começar, criamos a função decorador. O decorador toma in func como argumento. func é a função que estamos decorando.

def create_logger(func):
    # The function body goes here

Dentro da função decorator, vamos criar nossa função modificada que registrará o nome de func antes de executar func.

# Inside create_logger
def modified_func():
    print("Calling: ", func.__name__)
    func()

Em seguida, a função create_logger retornará a função modificada. Como resultado, toda a nossa função create_logger ficará assim:

def create_logger(func):
    def modified_func():
        print("Calling: ", func.__name__)
        func()

    return modified_function

Terminamos de criar o decorador. A função create_logger é um exemplo simples de uma função decoradora. Ele pega em func, que é a função que estamos decorando, e retorna outra função, modify_func. modify_func primeiro registra o nome de func, antes de executar func.

Como usar decoradores em Python

Para usar nosso decorador, usamos a sintaxe @ da seguinte forma:

@create_logger
def say_hello():
    print("Hello, World!")

Agora podemos chamar say_hello() em nosso script, e a saída deve ser o seguinte texto:

Calling:  say_hello
"Hello, World"

Mas o que o @create_logger está fazendo? Bem, está aplicando o decorator à nossa função say_hello. Para entender melhor o que está fazendo, o código imediatamente abaixo deste parágrafo alcançaria o mesmo resultado que colocar @create_logger antes de say_hello.

def say_hello():
    print("Hello, World!")

say_hello = create_logger(say_hello)

Em outras palavras, uma maneira de usar decoradores em Python é chamar explicitamente o decorador passando a função como fizemos no código acima. A outra maneira mais concisa é usar a sintaxe @.

Nesta seção, abordamos como criar decoradores Python.

Exemplos um pouco mais complicados

O exemplo acima foi um caso simples. Existem exemplos um pouco mais complexos, como quando a função que estamos decorando recebe argumentos. Outra situação mais complicada é quando você quer decorar uma turma inteira. Vou cobrir essas duas situações aqui.

Quando a função recebe argumentos

Quando a função que você está decorando recebe argumentos, a função modificada deve receber os argumentos e passá-los quando eventualmente fizer a chamada para a função não modificada. Se isso parece confuso, deixe-me explicar em termos foo-bar.

Lembre-se de que foo é a função do decorador, bar é a função que estamos decorando e baz é a barra decorada. Nesse caso, bar receberá os argumentos e os passará para baz durante a chamada para baz. Aqui está um exemplo de código para solidificar o conceito:

def foo(bar):
    def baz(*args, **kwargs):
        # You can do something here
        ___
        # Then we make the call to bar, passing in args and kwargs
        bar(*args, **kwargs)
        # You can also do something here
        ___

    return baz

Se os *args e **kwargs parecerem desconhecidos; eles são simplesmente ponteiros para os argumentos posicionais e palavras-chave, respectivamente.

É importante observar que baz tem acesso aos argumentos e pode, portanto, executar alguma validação dos argumentos antes de chamar bar.

Um exemplo seria se tivéssemos uma função decoradora, ensure_string, que garantiria que o argumento passado para uma função que está decorando seja uma string; nós implementaríamos assim:

def ensure_string(func):
    def decorated_func(text):
        if type(text) is not str:
             raise TypeError('argument to ' + func.__name__ + ' must be a string.')
        else:
             func(text)

    return decorated_func

Poderíamos decorar a função say_hello assim:

@ensure_string
def say_hello(name):
    print('Hello', name)

Então poderíamos testar o código usando isto:

say_hello('John') # Should run just fine
say_hello(3) # Should throw an exception

E deve produzir a seguinte saída:

Hello John
Traceback (most recent call last):
   File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, in <module> say hello(3) # should throw an exception
   File "/home/anesu/Documents/python-tu$ ./decorators.pytorial/./decorators.py", line 7, in decorated_func raise TypeError('argument to + func._name_ + must be a string.')
TypeError: argument to say hello must be a string. $0

Como esperado, o script conseguiu imprimir ‘Hello John’ porque ‘John’ é uma string. Ele lançou uma exceção ao tentar imprimir ‘Hello 3’ porque ‘3’ não era uma string. O decorador assegurar_string pode ser usado para validar os argumentos de qualquer função que requeira uma string.

  Como alterar facilmente maiúsculas e minúsculas no texto no Google Docs

Decorando uma aula

Além de apenas decorar as funções, também podemos decorar as aulas. Quando você adiciona um decorador a uma classe, o método decorado substitui o método construtor/iniciador da classe (__init__).

Voltando ao foo-bar, suponha que foo seja nosso decorador e Bar seja a classe que estamos decorando, então foo irá decorar Bar.__init__. Isso será útil se quisermos fazer algo antes que os objetos do tipo Bar sejam instanciados.

Isso significa que o seguinte código

def foo(func):
    def new_func(*args, **kwargs):
        print('Doing some stuff before instantiation')
        func(*args, **kwargs)

    return new_func

@foo
class Bar:
    def __init__(self):
        print("In initiator")

É equivalente a

def foo(func):
    def new_func(*args, **kwargs):
        print('Doing some stuff before instantiation')
        func(*args, **kwargs)

    return new_func

class Bar:
    def __init__(self):
        print("In initiator")


Bar.__init__ = foo(Bar.__init__)

Na verdade, instanciar um objeto da classe Bar, definido usando qualquer um dos dois métodos, deve fornecer a mesma saída:

Doing some stuff before instantiation
In initiator

Decoradores de exemplo em Python

Embora você possa definir seus próprios decoradores, existem alguns que já estão embutidos no Python. Aqui estão alguns dos decoradores comuns que você pode encontrar em Python:

@staticmethod

O método estático é usado em uma classe para indicar que o método que está decorando é um método estático. Métodos estáticos são métodos que podem ser executados sem a necessidade de instanciar a classe. No exemplo de código a seguir, criamos uma classe Dog com um método estático bark.

class Dog:
    @staticmethod
    def bark():
        print('Woof, woof!')

Agora o método bark pode ser acessado assim:

Dog.bark()

E executar o código produziria a seguinte saída:

Woof, woof!

Como mencionei na seção Como usar decoradores, os decoradores podem ser usados ​​de duas maneiras. A sintaxe @ sendo a mais concisa sendo uma das duas. O outro método é chamar a função decoradora, passando a função que queremos decorar como argumento. Significa que o código acima alcança a mesma coisa que o código abaixo:

class Dog:
    def bark():
        print('Woof, woof!')

Dog.bark = staticmethod(Dog.bark)

E ainda podemos usar o método bark da mesma forma

Dog.bark()

E produziria a mesma saída

Woof, woof!

Como você pode ver, o primeiro método é mais limpo e fica mais óbvio que a função é estática antes mesmo de você começar a ler o código. Como resultado, para os exemplos restantes, usarei o primeiro método. Mas lembre-se de que o segundo método é uma alternativa.

@classmethod

Este decorador é usado para indicar que o método que está decorando é um método de classe. Os métodos de classe são semelhantes aos métodos estáticos, pois ambos não exigem que a classe seja instanciada antes de serem chamados.

  Como configurar e usar o Apple Pay em um iPhone

No entanto, a principal diferença é que os métodos de classe têm acesso aos atributos de classe, enquanto os métodos estáticos não. Isso ocorre porque o Python passa automaticamente a classe como o primeiro argumento para um método de classe sempre que é chamado. Para criar um método de classe em Python, podemos usar o decorador classmethod.

class Dog:
    @classmethod
    def what_are_you(cls):
        print("I am a " + cls.__name__ + "!")

Para executar o código, basta chamar o método sem instanciar a classe:

Dog.what_are_you()

E a saída é:

I am a Dog!

@propriedade

O decorador de propriedade é usado para rotular um método como um configurador de propriedade. Voltando ao nosso exemplo de Cachorro, vamos criar um método que recupere o nome do Cachorro.

class Dog:
    # Creating a constructor method that takes in the dog's name
    def __init__(self, name):

         # Creating a private property name
         # The double underscores make the attribute private
         self.__name = name

    
    @property
    def name(self):
        return self.__name

Agora podemos acessar o nome do cachorro como uma propriedade normal,

# Creating an instance of the class
foo = Dog('foo')

# Accessing the name property
print("The dog's name is:", foo.name)

E o resultado da execução do código seria

The dog's name is: foo

@property.setter

O decorator property.setter é usado para criar um método setter para nossas propriedades. Para usar o decorador @property.setter, substitua property pelo nome da propriedade para a qual está criando um setter. Por exemplo, se você estiver criando um setter para o método da propriedade foo, seu decorador será @foo.setter. Aqui está um exemplo de cachorro para ilustrar:

class Dog:
    # Creating a constructor method that takes in the dog's name
    def __init__(self, name):

         # Creating a private property name
         # The double underscores make the attribute private
         self.__name = name

    
    @property
    def name(self):
        return self.__name

    # Creating a setter for our name property
    @name.setter
    def name(self, new_name):
        self.__name = new_name

Para testar o setter, podemos usar o seguinte código:

# Creating a new dog
foo = Dog('foo')

# Changing the dog's name
foo.name="bar"

# Printing the dog's name to the screen
print("The dog's new name is:", foo.name)

A execução do código produzirá a seguinte saída:

The dogs's new name is: bar

Importância dos decoradores em Python

Agora que abordamos o que são decoradores e você viu alguns exemplos de decoradores, podemos discutir por que os decoradores são importantes em Python. Os decoradores são importantes por vários motivos. Alguns deles, listei abaixo:

  • Eles permitem a reutilização de código: No exemplo de log fornecido acima, podemos usar o @create_logger em qualquer função que desejarmos. Isso nos permite adicionar a funcionalidade de registro a todas as nossas funções sem escrevê-la manualmente para cada função.
  • Eles permitem que você escreva código modular: Novamente, voltando ao exemplo de registro, com decoradores, você pode separar a função principal, neste caso, say_hello, da outra funcionalidade necessária, neste caso, o registro.
  • Eles aprimoram estruturas e bibliotecas: os decoradores são amplamente usados ​​em estruturas e bibliotecas Python para fornecer funcionalidade adicional. Por exemplo, em estruturas da Web como Flask ou Django, os decoradores são usados ​​para definir rotas, manipular autenticação ou aplicar middleware a visualizações específicas.

Palavras Finais

Os decoradores são incrivelmente úteis; você pode usá-los para estender funções sem alterar sua funcionalidade. Isso é útil quando você deseja cronometrar o desempenho de funções, registrar sempre que uma função é chamada, validar argumentos antes de chamar uma função ou verificar permissões antes de uma função ser executada. Depois de entender os decoradores, você poderá escrever código de maneira mais limpa.

Em seguida, você pode querer ler nossos artigos sobre tuplas e usar cURL em Python.