python - patron - ¿Decoradores con parámetros?
patron decorator python (7)
Tengo un problema con la transferencia de la variable ''insurance_mode'' por el decorador. Lo haría por la siguiente declaración 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
Aquí hay una versión ligeramente modificada de la respuesta de t.dubrownik . ¿Por qué?
- Como plantilla general, debe devolver el valor de retorno de la función original.
- 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
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(...):
...
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í.
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 decorador es una función, en realidad funciona como 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.
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 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(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
Here 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í.
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 x y z
in decorator after wrapee with flag foo de fa fa
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 invalida en la declaración 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)
<function _pseudo_decor.<locals>.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)
<function __main__.bar(*args, **kwargs)>