¿Decoradores con parámetros?
Tengo un problema con la transferencia de la variable insurance_mode
por parte del decorador. Lo haría mediante la siguiente declaración del decorador:
@execute_complete_reservation(True)
def test_booking_gta_object(self):
self.test_select_gta_object()
pero lamentablemente esta afirmación no funciona. Quizás 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
La sintaxis de los 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. Entonces 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 implementar esto usando objetos invocables y eso también se explica allí.
Editar : para comprender en profundidad el modelo mental de los decoradores, eche un vistazo a esta increíble charla sobre Pycon. Bien vale la pena los 30 minutos.
Una forma de pensar en los decoradores con argumentos es
@decorator
def foo(*args, **kwargs):
pass
se traduce en
foo = decorator(foo)
Entonces, si el decorador tuviera argumentos,
@decorator_with_args(arg)
def foo(*args, **kwargs):
pass
se traduce en
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 sencillo con parciales para facilitar la tarea de 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
se conviertereal_decorator(foo)
Un efecto de decorar una función es que el nombre foo
se anula tras 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 foo
metadatos de se anulan, 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 "elevar" la cadena de documentación 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):
# pre function execution stuff here, for eg.
print("decorator argument is %s" % str(argument))
returned_value = fun(*args, **kwargs)
# post execution stuff here, for eg.
print("returned value is %s" % returned_value)
return returned_value
return ret_fun
real_decorator1 = partial(_pseudo_decor, argument="some_arg")
real_decorator2 = partial(_pseudo_decor, argument="some_other_arg")
@real_decorator1
def bar(*args, **kwargs):
pass
>>> print(bar)
<function __main__.bar(*args, **kwargs)>
>>> bar(1,2,3, k="v", x="z")
decorator argument is some_arg
returned value is None