¿Decoradores con parámetros?

Tengo un problema con la transferencia de la variable ‘insurance_mode’ por el decorador. Lo haría por la siguiente statement de decorador:

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

pero lamentablemente, esta afirmación no funciona. Tal vez tal vez haya una 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 

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

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

Aquí puede leer más sobre el tema: también es posible implementarlo utilizando objetos que se pueden llamar y eso también se explica allí.

Una manera de pensar en decoradores con argumentos es

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

traduce a

 foo = decorator(foo) 

Así que si el decorador tenía argumentos,

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

traduce a

 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).

Utilizo un truco simple con parciales para facilitar mis decoradores

 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 

Actualizar:

Arriba, foo convierte en real_decorator(foo)

Un efecto de decorar una función es que el nombre foo se anula en la statement del decorador. foo se “invalida” por lo que devuelva real_decorator . En este caso, un nuevo objeto de función.

Todos los metadatos de foo están anulados, en particular la cadena de documentos y el nombre de la función.

 >>> print(foo) .ret_fun at 0x10666a2f0> 

functools.wraps nos brinda un método conveniente para “levantar” la cadena de documentos y el nombre de 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)  

Me gustaría mostrar una idea que es muy elegante en mi humilde opinión. La solución propuesta por t.dubrownik muestra un patrón que siempre es 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 regular para agregar parámetros. Entonces, por 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 con 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 de tipo seguro:

 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 de envoltura, pero recomendaría usarlo todo el tiempo.

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

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

Entonces usa @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 

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') 

Huellas dactilares:

 in decorator before wrapee with flag foo de fa fa in bar xyz in decorator after wrapee with flag foo de fa fa 

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

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

 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í.

 def decorator(argument): def real_decorator(function): def wrapper(*args): for arg in args: assert type(arg)==int,f'{arg} is not an interger' result = function(*args) result = result*argument return result return wrapper return real_decorator 

Uso del decorador.

 @decorator(2) def adder(*args): sum=0 for i in args: sum+=i return sum 

Entonces el

 adder(2,3) 

produce

 10 

pero

 adder('hi',3) 

produce

 --------------------------------------------------------------------------- AssertionError Traceback (most recent call last)  in  ----> 1 adder('hi',3)  in wrapper(*args) 3 def wrapper(*args): 4 for arg in args: ----> 5 assert type(arg)==int,f'{arg} is not an interger' 6 result = function(*args) 7 result = result*argument AssertionError: hi is not an interger 

Defina esta “función de decoración” para generar una función de decoración personalizada:

 def decoratorize(FUN, **kw): def foo(*args, **kws): return FUN(*args, **kws, **kw) return foo 

utilízalo de esta manera:

  @decoratorize(FUN, arg1 = , arg2 = , ...) def bar(...): ...