curso
Tutorial de decoradores Python
Um decorador é um padrão de design em Python que permite que um usuário adicione novas funcionalidades a um objeto existente sem modificar sua estrutura. Normalmente, os decoradores são aplicados a funções e desempenham um papel fundamental no aprimoramento ou na modificação do comportamento das funções. Tradicionalmente, os decoradores são colocados antes da definição de uma função que você deseja decorar. Neste tutorial, demonstraremos como usar decoradores de forma eficaz nas funções Python.
As funções em Python são cidadãos de primeira classe. Isso significa que eles suportam operações como ser passado como argumento, retornado de uma função, modificado e atribuído a uma variável. Essa propriedade é fundamental, pois permite que as funções sejam tratadas como qualquer outro objeto no Python, possibilitando maior flexibilidade na programação.
Para executar facilmente todos os exemplos de código deste tutorial, você pode criar gratuitamente uma pasta de trabalho do DataLab que tenha o Python pré-instalado e contenha todos os exemplos de código. Para praticar mais os decoradores, confira este exercício prático do DataCamp.
Aprenda Python do zero
Atribuição de funções a variáveis
Para começar, criamos uma função que adicionará um a um número sempre que for chamada. Em seguida, atribuiremos a função a uma variável e usaremos essa variável para chamar a função.
def plus_one(number):
return number + 1
add_one = plus_one
add_one(5)
6
Definição de funções dentro de outras funções
A seguir, ilustraremos como você pode definir uma função dentro de outra função em Python. Fique comigo, pois em breve você descobrirá como tudo isso é relevante para criar e entender os decoradores em Python.
def plus_one(number):
def add_one(number):
return number + 1
result = add_one(number)
return result
plus_one(4)
5
Passagem de funções como argumentos para outras funções
As funções também podem ser passadas como parâmetros para outras funções. Vamos ilustrar isso a seguir.
def plus_one(number):
return number + 1
def function_call(function):
number_to_add = 5
return function(number_to_add)
function_call(plus_one)
6
Funções que retornam outras funções
Uma função também pode gerar outra função. Mostraremos isso a seguir com um exemplo.
def hello_function():
def say_hi():
return "Hi"
return say_hi
hello = hello_function()
hello()
'Hi'
Entendendo os fechamentos
O Python permite que uma função aninhada acesse o escopo externo da função que a envolve. Esse é um conceito essencial nos decoradores, conhecido como fechamento.
Uma closure em Python é uma função que lembra o ambiente em que foi criada, mesmo depois que esse ambiente não estiver mais ativo. Isso significa que uma função aninhada pode "fechar" as variáveis do escopo que a envolve e continuar a usá-las.
Os fechamentos são essenciais para que você entenda os decoradores, pois eles dependem da capacidade de uma função de invólucro aninhada para acessar e modificar o estado da função de decorador envolvente.
Exemplo de um fechamento:
def outer_function(message):
def inner_function():
print(f"Message from closure: {message}")
return inner_function
closure_function = outer_function("Hello, closures!")
closure_function()
# Output: Message from closure: Hello, closures!
Neste exemplo:
inner_function
é um fechamento porque acessamessage
, uma variável de seu escopo de fechamento (outer_function
).- Mesmo que
outer_function
tenha terminado de ser executado,inner_function
mantém o acesso amessage
.
Quando você cria um decorador, a função de invólucro (dentro do decorador) é um fechamento. Ele mantém o acesso à função que está sendo decorada e a qualquer estado ou argumento adicional definido na função decoradora. Por exemplo:
def simple_decorator(func):
def wrapper():
print("Before the function call")
func()
print("After the function call")
return wrapper
@simple_decorator
def greet():
print("Hello!")
greet()
# Output:
# Before the function call
# Hello!
# After the function call
Aqui, wrapper
é um fechamento que lembra a função greet
e adiciona comportamento antes e depois de sua execução.
Criando Decoradores
Com esses pré-requisitos fora do caminho, vamos criar um decorador simples que converterá uma frase em maiúsculas. Fazemos isso definindo um wrapper dentro de uma função anexa. Como você pode ver, é muito semelhante à função dentro de outra função que criamos anteriormente.
def uppercase_decorator(function):
def wrapper():
func = function()
make_uppercase = func.upper()
return make_uppercase
return wrapper
Nossa função de decorador recebe uma função como argumento e, portanto, devemos definir uma função e passá-la ao nosso decorador. Aprendemos anteriormente que podemos atribuir uma função a uma variável. Usaremos esse truque para chamar nossa função de decorador.
def say_hi():
return 'hello there'
decorate = uppercase_decorator(say_hi)
decorate()
'HELLO THERE'
No entanto, o Python oferece uma maneira muito mais fácil de aplicar decoradores. Basta usar o símbolo @ antes da função que você deseja decorar. Vamos mostrar isso na prática a seguir.
@uppercase_decorator
def say_hi():
return 'hello there'
say_hi()
'HELLO THERE'
Aplicação de vários decoradores a uma única função
Você pode usar vários decoradores em uma única função. No entanto, os decoradores serão aplicados na ordem em que os chamamos. A seguir, definiremos outro decorador que divide a frase em uma lista. Em seguida, aplicaremos os decoradores uppercase_decorator
e split_string
a uma única função.
import functools
def split_string(function):
@functools.wraps(function)
def wrapper():
func = function()
splitted_string = func.split()
return splitted_string
return wrapper
@split_string
@uppercase_decorator
def say_hi():
return 'hello there'
say_hi()
['HELLO', 'THERE']
Na saída acima, notamos que a aplicação dos decoradores é feita de baixo para cima. Se tivéssemos trocado a ordem, teríamos visto um erro, pois as listas não têm um atributo upper
. Primeiro, a frase foi convertida em letras maiúsculas e depois dividida em uma lista.
Observação: Ao empilhar decoradores, é uma prática comum usar o site functools.wraps
para garantir que os metadados da função original sejam preservados durante todo o processo de empilhamento. Isso ajuda a manter a clareza e a consistência na depuração e na compreensão das propriedades da função decorada.
Aceitação de argumentos em funções do Decorator
Às vezes, talvez seja necessário definir um decorador que aceite argumentos. Para isso, você passa os argumentos para a função wrapper. Os argumentos serão então passados para a função que está sendo decorada no momento da chamada.
def decorator_with_arguments(function):
def wrapper_accepting_arguments(arg1, arg2):
print("My arguments are: {0}, {1}".format(arg1,arg2))
function(arg1, arg2)
return wrapper_accepting_arguments
@decorator_with_arguments
def cities(city_one, city_two):
print("Cities I love are {0} and {1}".format(city_one, city_two))
cities("Nairobi", "Accra")
My arguments are: Nairobi, Accra
Cities I love are Nairobi and Accra
Observação: É essencial que você garanta que o número de argumentos no decorador (arg1, arg2
neste exemplo) corresponda ao número de argumentos na função envolvida (cities
neste exemplo). Esse alinhamento é fundamental para evitar erros e garantir a funcionalidade adequada ao usar decoradores com argumentos.
Definição de decoradores de uso geral
Para definir um decorador de uso geral que possa ser aplicado a qualquer função, usamos args
e **kwargs
. args
e **kwargs
coletam todos os argumentos posicionais e de palavra-chave e os armazenam nas variáveis args e kwargs. args
e kwargs
nos permitem passar quantos argumentos quisermos durante as chamadas de função.
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
def a_wrapper_accepting_arbitrary_arguments(*args,**kwargs):
print('The positional arguments are', args)
print('The keyword arguments are', kwargs)
function_to_decorate(*args)
return a_wrapper_accepting_arbitrary_arguments
@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
print("No arguments here.")
function_with_no_argument()
The positional arguments are ()
The keyword arguments are {}
No arguments here.
Vamos ver como usar o decorador usando argumentos posicionais.
@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
print(a, b, c)
function_with_arguments(1,2,3)
The positional arguments are (1, 2, 3)
The keyword arguments are {}
1 2 3
Os argumentos de palavras-chave são passados usando palavras-chave. Uma ilustração disso é mostrada abaixo.
@a_decorator_passing_arbitrary_arguments
def function_with_keyword_arguments():
print("This has shown keyword arguments")
function_with_keyword_arguments(first_name="Derrick", last_name="Mwiti")
The positional arguments are ()
The keyword arguments are {'first_name': 'Derrick', 'last_name': 'Mwiti'}
This has shown keyword arguments
Observação: O uso de **kwargs
no decorador permite que ele manipule argumentos de palavras-chave. Isso torna o decorador de uso geral versátil e capaz de lidar com uma variedade de tipos de argumentos durante as chamadas de função.
Passagem de argumentos para o Decorator
Agora vamos ver como passar argumentos para o decorador em si. Para isso, definimos um criador de decoradores que aceita argumentos e, em seguida, definimos um decorador dentro dele. Em seguida, definimos uma função wrapper dentro do decorador, como fizemos anteriormente.
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):
def decorator(func):
def wrapper(function_arg1, function_arg2, function_arg3) :
"This is the wrapper function"
print("The wrapper can access all the variables\n"
"\t- from the decorator maker: {0} {1} {2}\n"
"\t- from the function call: {3} {4} {5}\n"
"and pass them to the decorated function"
.format(decorator_arg1, decorator_arg2,decorator_arg3,
function_arg1, function_arg2,function_arg3))
return func(function_arg1, function_arg2,function_arg3)
return wrapper
return decorator
pandas = "Pandas"
@decorator_maker_with_arguments(pandas, "Numpy","Scikit-learn")
def decorated_function_with_arguments(function_arg1, function_arg2,function_arg3):
print("This is the decorated function and it only knows about its arguments: {0}"
" {1}" " {2}".format(function_arg1, function_arg2,function_arg3))
decorated_function_with_arguments(pandas, "Science", "Tools")
The wrapper can access all the variables
- from the decorator maker: Pandas Numpy Scikit-learn
- from the function call: Pandas Science Tools
and pass them to the decorated function
This is the decorated function, and it only knows about its arguments: Pandas Science Tools
Depuração de decoradores
Como já observamos, os decoradores envolvem as funções. O nome da função original, sua docstring e a lista de parâmetros são ocultados pelo fechamento do wrapper: Por exemplo, quando tentarmos acessar os metadados do decorated_function_with_arguments
, veremos os metadados do fechamento do wrapper. Isso representa um desafio para a depuração.
decorated_function_with_arguments.__name__
'wrapper'
decorated_function_with_arguments.__doc__
'This is the wrapper function'
Para resolver esse desafio, o Python fornece um decorador functools.wraps
. Esse decorador copia os metadados perdidos da função não decorada para o fechamento decorado. Vamos mostrar como você faria isso.
import functools
def uppercase_decorator(func):
@functools.wraps(func)
def wrapper():
return func().upper()
return wrapper
@uppercase_decorator
def say_hi():
"This will say hi"
return 'hello there'
say_hi()
'HELLO THERE'
Quando verificamos os metadados do say_hi
, percebemos que agora ele está se referindo aos metadados da função e não aos metadados do wrapper.
say_hi.__name__
'say_hi'
say_hi.__doc__
'This will say hi'
É aconselhável e uma boa prática que você sempre use functools.wraps
ao definir decoradores. Isso poupará a você muita dor de cabeça na depuração.
Decoradores baseados em classe
Embora os decoradores baseados em funções sejam comuns, o Python também permite que você crie decoradores baseados em classes, que oferecem maior flexibilidade e facilidade de manutenção, especialmente para casos de uso complexos. Um decorador baseado em classe é uma classe com um método __call__
que permite que ela se comporte como uma função.
class UppercaseDecorator:
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
result = self.function(*args, **kwargs)
return result.upper()
@UppercaseDecorator
def greet():
return "hello there"
print(greet())
# Output: HELLO THERE
Como funciona:
- O método
__init__
inicializa o decorador com a função a ser decorada. - O método
__call__
é invocado quando a função decorada é chamada, permitindo que o decorador modifique seu comportamento.
Vantagens dos decoradores baseados em classes:
- Decoradores com estado: Os decoradores baseados em classes podem manter o estado usando variáveis de instância, ao contrário dos decoradores baseados em funções, que exigem fechamentos ou variáveis globais.
- Legibilidade: Para decoradores complexos, o encapsulamento da lógica em uma classe pode tornar o código mais organizado e mais fácil de entender.
Exemplo de um decorador com estado:
class CallCounter:
def __init__(self, function):
self.function = function
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Function {self.function.__name__} has been called {self.count} times.")
return self.function(*args, **kwargs)
@CallCounter
def say_hello():
print("Hello!")
say_hello()
say_hello()
# Output:
# Function say_hello has been called 1 times.
# Hello!
# Function say_hello has been called 2 times.
# Hello!
Caso de uso do Decorator no mundo real: Armazenamento em cache
O decorador lru_cache
é uma ferramenta interna do Python que armazena em cache os resultados de chamadas de funções caras. Isso melhora o desempenho, evitando cálculos redundantes para entradas repetidas.
Exemplo:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # Subsequent calls with the same argument are much faster
Outros usos comuns para decoradores:
-
Registro em log: Rastreie chamadas de função, argumentos e valores de retorno para depuração ou auditoria.
-
Autenticação: Imponha o controle de acesso em aplicativos da Web como Flask ou Django.
-
Tempo de execução: Meça e otimize o tempo de execução da função para tarefas de desempenho crítico.
-
Mecanismo de repetição: Repetir automaticamente as chamadas de função com falha, o que é útil em operações de rede.
-
Validação de entrada: Validar os argumentos da função antes da execução.
Resumo dos decoradores Python
Os decoradores alteram dinamicamente a funcionalidade de uma função, método ou classe sem precisar usar diretamente as subclasses ou alterar o código-fonte da função que está sendo decorada. O uso de decoradores em Python também garante que seu código seja DRY (Don't Repeat Yourself). Os decoradores têm vários casos de uso, como:
- Autorização em estruturas Python, como Flask e Django
- Registro em log
- Medição do tempo de execução
- Sincronização
Para saber mais sobre os decoradores do Python, consulte a Biblioteca de decoradores do Python.
Torne-se um desenvolvedor Python
Perguntas frequentes
Há alguma consideração de desempenho ao usar decoradores?
Sim, os decoradores podem aumentar a sobrecarga porque introduzem chamadas de função adicionais. Quando o desempenho é crítico, é importante considerar essa sobrecarga, especialmente se a função decorada for chamada com frequência em um contexto sensível ao desempenho.
Os decoradores podem ser usados com métodos de classe e, em caso afirmativo, como?
Sim, os decoradores podem ser aplicados a métodos de classe, assim como as funções comuns. O decorador receberá o método como argumento e retornará um novo método ou uma versão modificada do método. Isso é normalmente usado para registro, controle de acesso ou imposição de pré-condições.
Como os decoradores podem ser usados para fins de registro?
Os decoradores podem ser usados para registrar chamadas de função, seus argumentos e valores de retorno, envolvendo a execução da função com código que registra esses detalhes em um sistema de registro. Isso ajuda no rastreamento e na depuração.
Qual é o significado do símbolo @ em decoradores?
O símbolo@
é um açúcar sintático em Python que simplifica a aplicação de um decorador a uma função. Ele permite que você aplique um decorador a uma função diretamente acima de sua definição, tornando o código mais limpo e legível.
Um decorador pode modificar o valor de retorno de uma função, e como isso funcionaria?
Sim, um decorador pode modificar o valor de retorno de uma função alterando a instrução de retorno dentro da função de invólucro. Por exemplo, ele pode transformar o tipo de dados de saída, formatá-lo ou adicionar processamento adicional antes de retornar o resultado final.
Como o Python lida com o escopo da variável quando uma função aninhada acessa uma variável da função que a envolve?
O Python usa uma regra de escopo LEGB (Local, Enclosing, Global, Built-in). No caso de funções aninhadas, a função aninhada pode acessar variáveis do escopo da função que a envolve, o que permite fechamentos em que a função interna mantém o acesso às variáveis da função externa mesmo depois que a função externa termina de ser executada.
Saiba mais sobre Python
curso
Writing Efficient Python Code
curso
Introduction to Functions in Python
tutorial
Tutorial de funções Python
tutorial
Declarações IF, ELIF e ELSE do Python
tutorial
Tutorial de indexação de lista Python()
tutorial
Função do sublinhado (_) no tutorial de Python
tutorial