Decoradores con parámetros?


Tengo un problema con la transferencia de la variable 'insurance_mode' por el decorador. Lo haría por la siguiente declaración decorador:

 @execute_complete_reservation(True)
 def test_booking_gta_object(self):
     self.test_select_gta_object()

Pero desafortunadamente, esta declaración no funciona. Tal vez tal vez hay mejor manera de resolver este problema.

def execute_complete_reservation(test_case,insurance_mode):
    def inner_function(self,*args,**kwargs):
        self.test_create_qsf_query()
        test_case(self,*args,**kwargs)
        self.test_select_room_option()
        if insurance_mode:
            self.test_accept_insurance_crosseling()
        else:
            self.test_decline_insurance_crosseling()
        self.test_configure_pax_details()
        self.test_configure_payer_details

    return inner_function
Author: martineau, 2011-05-08

6 answers

Quieres decir def test_booking_gta_object, ¿verdad? De todos modos, la sintaxis para decoradores con argumentos es un poco diferente - el decorador con argumentos debe devolver una función que tome una función y devuelva otra función. Así que realmente debería devolver un decorador normal. Un poco confuso, ¿verdad? Lo que quiero decir es:

def decorator(argument):
    def real_decorator(function):
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            result = function(*args, **kwargs)
            more_funny_stuff()
            return result
        return wrapper
    return real_decorator

Aquí puede leer más sobre el tema - también es posible implementar esto usando objetos llamables y eso también se explica allí.

 474
Author: t.dubrownik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-21 18:38:44

Una forma de pensar acerca de los decoradores con argumentos es{[15]]}

@decorator
def foo(*args, **kwargs):
    pass

Se traduce como

foo = decorator(foo)

Así que si el decorador tenía argumentos,{[15]]}

@decorator_with_args(arg)
def foo(*args, **kwargs):
    pass

Se traduce como

foo = decorator_with_args(arg)(foo)

decorator_with_args es una función que acepta un argumento personalizado y que devuelve el decorador real (que se aplicará a la función decorada).

Uso un truco simple con parciales para hacer que mis decoradores sean fáciles

from functools import partial

def _pseudo_decor(fun, argument):
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def foo(*args, **kwargs):
    pass

Actualización:

Anterior, foo se convierte en real_decorator(foo)

Un efecto de decorar una función es que el nombre foo se anula en la declaración del decorador. foo es "anulado" por lo que sea devuelto por real_decorator. En este caso, un nuevo objeto de función.

Todos los metadatos de foo están sobreescritos, notablemente docstring y function name.

>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>

Functools.wraps nos da un método conveniente para "levantar" el docstring y el nombre a la función devuelta.

from functools import partial, wraps

def _pseudo_decor(fun, argument):
    # magic sauce to lift the name and doc of the function
    @wraps(fun)
    def ret_fun(*args, **kwargs):
        #do stuff here, for eg.
        print ("decorator arg is %s" % str(argument))
        return fun(*args, **kwargs)
    return ret_fun

real_decorator = partial(_pseudo_decor, argument=arg)

@real_decorator
def bar(*args, **kwargs):
    pass

>>> print(bar)
<function __main__.bar(*args, **kwargs)>
 196
Author: srj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-28 02:31:06

Me gustaría mostrar una idea que en mi humilde opinión es bastante elegante. La solución propuesta por t.dubrownik muestra un patrón que es siempre el mismo: necesita la envoltura de tres capas independientemente de lo que haga el decorador.

Así que pensé que este es un trabajo para un meta-decorador, es decir, un decorador para decoradores. Como un decorador es una función, en realidad funciona como un decorador regular con argumentos:

def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

Esto se puede aplicar a un decorador normal para agregar parámetros. Así que para ejemplo, digamos que tenemos el decorador que duplica el resultado de una función:

def double(f):
    def aux(*xs, **kws):
        return 2 * f(*xs, **kws)
    return aux

@double
def function(a):
    return 10 + a

print function(3)    # Prints 26, namely 2 * (10 + 3)

Con @parametrized podemos construir un decorador genérico @multiply que tenga un parámetro

@parametrized
def multiply(f, n):
    def aux(*xs, **kws):
        return n * f(*xs, **kws)
    return aux

@multiply(2)
def function(a):
    return 10 + a

print function(3)    # Prints 26

@multiply(3)
def function_again(a):
    return 10 + a

print function(3)          # Keeps printing 26
print function_again(3)    # Prints 39, namely 3 * (10 + 3)

Convencionalmente el primer parámetro de un decorador parametrizado es la función, mientras que los argumentos restantes corresponderán al parámetro del decorador parametrizado.

Un ejemplo de uso interesante podría ser un decorador asertivo seguro de tipo:

import itertools as it

@parametrized
def types(f, *types):
    def rep(*args):
        for a, t, n in zip(args, types, it.count()):
            if type(a) is not t:
                raise TypeError('Value %d has not type %s. %s instead' %
                    (n, t, type(a))
                )
        return f(*args)
    return rep

@types(str, int)  # arg1 is str, arg2 is int
def string_multiply(text, times):
    return text * times

print(string_multiply('hello', 3))    # Prints hellohellohello
print(string_multiply(3, 3))          # Fails miserably with TypeError

Una nota final: aquí no estoy usando functools.wraps para las funciones wrapper, pero yo recomendaría usarlo todas las veces.

 59
Author: Dacav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-20 12:39:51

Aquí hay una versión ligeramente modificada de la respuesta de t.dubrownik. ¿Por qué?

  1. Como plantilla general, debe devolver el valor devuelto de la función original.
  2. Esto cambia el nombre de la función, lo que podría afectar a otros decoradores / código.

Así que use @functools.wraps():

from functools import wraps

def decorator(argument):
    def real_decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            funny_stuff()
            something_with_argument(argument)
            retval = function(*args, **kwargs)
            more_funny_stuff()
            return retval
        return wrapper
    return real_decorator
 44
Author: Ross R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-22 10:44:05

Supongo que su problema es pasar argumentos a su decorador. Esto es un poco complicado y no sencillo.

Aquí hay un ejemplo de cómo hacer esto:

class MyDec(object):
    def __init__(self,flag):
        self.flag = flag
    def __call__(self, original_func):
        decorator_self = self
        def wrappee( *args, **kwargs):
            print 'in decorator before wrapee with flag ',decorator_self.flag
            original_func(*args,**kwargs)
            print 'in decorator after wrapee with flag ',decorator_self.flag
        return wrappee

@MyDec('foo de fa fa')
def bar(a,b,c):
    print 'in bar',a,b,c

bar('x','y','z')

Impresiones:

in decorator before wrapee with flag  foo de fa fa
in bar x y z
in decorator after wrapee with flag  foo de fa fa

Vea el artículo de Bruce Eckel para más detalles.

 35
Author: Ross Rogers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-20 12:37:27

En mi caso, decidí resolver esto a través de una lambda de una línea para crear una nueva función de decorador:

def finished_message(function, message="Finished!"):

    def wrapper(*args, **kwargs):
        output = function(*args,**kwargs)
        print(message)
        return output

    return wrapper

@finished_message
def func():
    pass

my_finished_message = lambda f: finished_message(f, "All Done!")

@my_finished_message
def my_func():
    pass

if __name__ == '__main__':
    func()
    my_func()

Cuando se ejecuta, esto imprime:

Finished!
All Done!

Quizás no tan extensible como otras soluciones, pero funcionó para mí.

 0
Author: ZacBook,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-15 19:21:52