Trilhas
/
Python
Python
/
Programa
/
Decoradores
De

Decoradores em Python

{one: "1 exercício", many: "%{count} de exercícios", other: "%{count} exercícios"}

Sobre Decoradores

Decoradores são funções que recebem outra função como argumento com o objetivo de estender ou substituir o comportamento da função passada. Se a função A é um decorador e a função B é o seu argumento, então a função A modifica, estende ou substitui o comportamento da função B sem modificar o código da função B. Dizemos que a função decoradora A envolve a função B. Embora falemos em "modificar" o comportamento, a função envolvida não é de fato alterada. O comportamento ou é adicionado em volta da função envolvida (e do que ela retorna), ou o comportamento da função envolvida é substituído por algum outro comportamento.

Um decorador é uma função de ordem superior

Uma função de ordem superior é uma função que aceita uma ou mais funções como argumentos e/ou retorna uma ou mais funções. Uma função usada como argumento ou retornada por outra função é uma função de primeira classe. Uma função Python, como objeto chamável, é uma função de primeira classe que pode ser guardada em uma variável ou usada como argumento, assim como qualquer outro valor ou objeto. Funções de ordem superior e funções de primeira classe trabalham juntas para tornar os decoradores possíveis.

Como é usar um decorador

O símbolo @ é colocado antes do nome da função decoradora, logo acima da função a ser decorada, assim:

@decorator
def decorated_function():
    pass

Alguns decoradores aceitam argumentos:

@decorator_with_arg(name="Bob")
def decorated_function2():
    pass

Se um decorador tiver argumentos padrão definidos, você precisa usar parênteses na chamada @decorator() para que o decorador funcione, como faria ao chamar qualquer função:

@decorator_with_default_arg()
def decorated_function3():
    pass

Se um decorador recebe argumentos posicionais, não informar os argumentos vai gerar um erro parecido com este:

TypeError: decorator_with_pos_arg() missing 1 required positional argument: 'name'

A sintaxe @decorator é um açúcar sintático, ou uma forma abreviada de chamar a função decoradora e passar a função decorada para ela como argumento. A seguir estão exemplos de formas alternativas de chamar um decorador:

def function():
    pass

function = decorator(function)


def function2():
    pass

function2 = decorator_with_arg(name="Bob")(function2)


def function3():
    pass

function3 = decorator_with_default_arg()(function3)

Escrevendo um decorador simples

A maioria dos decoradores tem a intenção de estender ou substituir o comportamento de outra função, mas alguns decoradores podem não fazer nada além de retornar as funções que estão envolvendo.

Decoradores são funções que recebem pelo menos um argumento: a função que estão envolvendo. Normalmente retornam a função envolvida ou o resultado de uma expressão que usa a função envolvida.

Um decorador simples, que apenas retorna a função que envolve, pode ser escrito assim:

>>> def do_nothing(func):
...     return func
...
... @do_nothing
... def function4():
...     return 4
...
>>> print(function4())
4

Um decorador pode apenas adicionar efeitos colaterais, como informações adicionais usadas para logging:

>>> def my_logger(func):
...     print(f"Entering {func.__name__}")
...     return func
...
... @my_logger
... def my_func():
...     print("Hello")
...
>>> my_func()
Entering my_func
Hello

Um decorador não retorna a si mesmo. Ele pode retornar seus argumentos de função, outra função, ou um ou mais valores que substituem o retorno da função passada ou decorada. Se um decorador retornar outra função, normalmente vai retornar uma função interna.

Funções internas

Uma função pode ser definida dentro de outra função. Essa função aninhada é chamada de função interna. Um decorador pode usar uma função interna para envolver seu argumento de função. O decorador então retorna sua função interna. A função interna pode então retornar o argumento de função original.

Um decorador de validação que usa uma função interna

A seguir está um exemplo de um decorador usado para validação:

def my_validator(func):
    def my_wrapper(world):
        print(f"Entering {func.__name__} with {world} argument")
        
        if world == "Pluto":
            print("Pluto is not a planet!")
        else:
            return func(world)
    return my_wrapper

@my_validator
def my_func(planet):
    print(f"Hello, {planet}!")

my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!

my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Pluto is not a planet!

Na primeira linha, temos a definição do argumento func do decorador. Na linha seguinte está a definição da função interna do decorador, que envolve o argumento func. Como a função interna envolve o argumento func do decorador, ela recebe o mesmo argumento que é passado para func. Repare que o envoltório não precisa usar o mesmo nome para o argumento que foi definido em func. A função original usa planet e o decorador usa world, e o decorador continua funcionando.

A função interna ou retorna func ou, se world == "Pluto", imprime que Plutão não é um planeta. Ela poderia ser escrita para lançar um ValueError em vez disso. Então, a função interna envolve func e ou retorna func ou faz algo que substitui o que func faria. O decorador retorna sua função interna. A função interna pode ou não retornar a função original passada. Dependendo de qual código é executado condicionalmente no envoltório ou na função interna, func pode ser retornado, um erro pode ser lançado, ou um valor do tipo de retorno de func pode ser retornado.

Decorando uma função que recebe um número arbitrário de argumentos

É possível escrever decoradores para funções que recebem um número arbitrário de argumentos usando a sintaxe *args e **kwargs.

A seguir está um exemplo de um decorador para uma função que recebe um número arbitrário de argumentos:

def double(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) * 2
    return wrapper

@double
def add(*args):
    return sum(args)

print(add(2, 3, 4))
#-> 18

print(add(2, 3, 4, 5, 6))
#-> 40

Isso funciona para dobrar o valor de retorno da função passada como argumento. Se quisermos triplicar, quadruplicar etc. o valor de retorno, podemos adicionar um parâmetro ao próprio decorador, como mostramos na próxima seção.

Decoradores que têm seus próprios parâmetros

A seguir está um exemplo de um decorador que pode ser configurado para multiplicar o valor de retorno da função decorada por um valor arbitrário:

def multi(factor=1):
     if factor == 0:
        raise ValueError("factor must not be 0")

     def outer_wrapper(func):
         def inner_wrapper(*args, **kwargs):
             return func(*args, **kwargs) * factor
         return inner_wrapper
     return outer_wrapper

@multi(factor=3)
def add(*args):
    return sum(args)

print(add(2, 3, 4))
#-> 27

print(add(2, 3, 4, 5, 6))
#-> 60

As primeiras linhas validam que factor não é 0. Depois, o envoltório externo é definido. Ele tem a mesma assinatura que esperamos de um decorador sem parâmetros. O envoltório externo tem uma função interna com a mesma assinatura da função original. O envoltório interno faz o trabalho de multiplicar o valor retornado pela função original pelo argumento passado ao decorador. O envoltório externo retorna o envoltório interno, e o decorador retorna o envoltório externo.

A seguir está um exemplo de um decorador com parâmetros que controla se ele valida o argumento passado para a função original:

def check_for_pluto(check=True):
    def my_validator(func):
        def my_wrapper(world):
            print(f"Entering {func.__name__} with {world} argument")
            if check and world == "Pluto":
                print("Pluto is not a planet!")
            else:
                return func(world)
        
        return my_wrapper
    return my_validator

@check_for_pluto(check=False)
def my_func(planet):
    print(f"Hello, {planet}!")

my_func("World")
#-> Entering my_func with World argument
#-> Hello, World!

my_func("Pluto")
#-> Entering my_func with Pluto argument
#-> Hello, Pluto!

Isso permite alternar facilmente entre verificar Pluto ou não, sem precisar modificar my_func.

Editar via GitHub O link abre em uma nova janela ou aba