Декораторы с параметрами?
у меня проблема с передачей переменной 'insurance_mode' декоратором. Я бы сделал это следующим заявлением декоратора:
@execute_complete_reservation(True)
def test_booking_gta_object(self):
self.test_select_gta_object()
но, к сожалению, это утверждение не работает. Возможно, есть лучший способ решить эту проблему.
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
6 ответов:
Вы имеете в виду
def test_booking_gta_object
, да? Во всяком случае, синтаксис для декораторов с аргументами немного отличается - декоратор с аргументами должен возвращать функцию, которая будет взять функцию и возвращает другую функцию. Так что он действительно должен вернуть нормального декоратора. Немного запутанно, правда? Что я имею в виду: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
здесь вы можете прочитать больше по этому вопросу - это также можно реализовать с помощью вызываемых объектов, и это также объясняется там.
один из способов думать о декораторах с аргументами
@decorator def foo(*args, **kwargs): pass
переводится как
foo = decorator(foo)
так что если у декоратора были аргументы,
@decorator_with_args(arg) def foo(*args, **kwargs): pass
переводится как
foo = decorator_with_args(arg)(foo)
decorator_with_args
- это функция, которая принимает пользовательский аргумент и возвращает фактический декоратор (который будет применен к украшенной функции).я использую простой трюк с частичными, чтобы сделать мои декораторы легко
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
обновление:
выше
foo
становитсяreal_decorator(foo)
одним из эффектов украшения функции является то, что имя
foo
переопределяется при объявлении декоратора.foo
"переопределяется" тем, что возвращаетсяreal_decorator
. В этом случае создается новый объект функции.все
foo
метаданные переопределяются, в частности docstring и имя функции.>>> print(foo) <function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>
functools.обертывания дает нам удобный способ "поднять" строку документа и имя возвращаемой функции.
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)>
Я хотел бы показать идею, которая является ИМХО довольно элегантно. Решение, предложенное т. Дубровником, показывает образец, который всегда один и тот же: вам нужна трехслойная обертка независимо от того, что делает декоратор.
поэтому я подумал, что это работа для мета-декоратора, то есть декоратора для декораторов. Как декоратор-это функция, она фактически работает как обычный декоратор с аргументами:
def parametrized(dec): def layer(*args, **kwargs): def repl(f): return dec(f, *args, **kwargs) return repl return layer
это может быть применено к обычному декоратору, чтобы добавить параметры. Так, например, у нас есть декоратор, который удваивает результат функции:
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)
С
@parametrized
мы можем построить универсальную@multiply
декоратор с параметром@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)
условно первый параметр a параметризованных decorator-это функция, а остальные аргументы будут соответствовать параметру параметризованного декоратора.
интересным примером использования может быть типобезопасный assertive декоратор:
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
последнее примечание: здесь я не использую
functools.wraps
функции-обертки, но я бы рекомендовал использовать его все время.
вот немного измененная версия ответ т. Дубровника. Зачем?
- в качестве общего шаблона, вы должны вернуть возвращаемое значение исходной функции.
- изменяет имя функции, которая может повлиять на другие декораторы / код.
чтобы использовать
@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
Я предполагаю, что ваша проблема заключается в передаче аргументов вашему декоратору. Это немного сложно и не просто.
вот пример того, как это сделать:
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')
принты:
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
см. статью Брюса Экеля для получения более подробной информации.
в моем случае я решил решить это с помощью однострочной лямбды, чтобы создать новую функцию декоратора:
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()
при выполнении этого отпечатки:
Finished! All Done!
возможно, не такой расширяемый, как другие решения, но работал для меня.