Декораторы с параметрами?
У меня проблема с переносом переменной "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
Ответы
Ответ 1
Синтаксис для декораторов с аргументами немного другой - декоратор с аргументами должен возвращать функцию, которая будет принимать функцию и возвращать другую функцию. Так что действительно должен вернуть нормальный декоратор. Немного смущает, верно? Я имею в виду:
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
Здесь вы можете прочитать больше на эту тему - это также можно реализовать с помощью вызываемых объектов, что также объясняется там.
Ответ 2
Отредактируйте: для более глубокого понимания ментальной модели декораторов взгляните на этот потрясающий Pycon Talk. стоит 30 минут.
Один из способов думать об декораторах с аргументами - это
@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
переопределены, в частности строка документации и имя функции.
>>> print(foo)
<function _pseudo_decor.<locals>.ret_fun at 0x10666a2f0>
functools.wraps дает нам удобный способ "поднять" строку документации и имя возвращаемой функции.
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)>
Ответ 3
Я хотел бы показать идею, которая ИМХО довольно изящна. Решение, предложенное t.dubrownik, показывает шаблон, который всегда один и тот же: вам нужна трехслойная обертка, независимо от того, что делает декоратор.
Поэтому я подумал, что это работа для мета-декоратора, то есть декоратора для декораторов. Поскольку декоратор является функцией, он фактически работает как обычный декоратор с аргументами:
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)
Обычно первым параметром параметризованного декоратора является функция, а остальные аргументы будут соответствовать параметру параметризованного декоратора.
Интересным примером использования может быть надежный напористый декоратор:
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
для функций оболочки, но я бы рекомендовал использовать его все время.
Ответ 4
Вот немного измененная версия ответа t.dubrownik. Зачем?
- В качестве общего шаблона вы должны вернуть возвращаемое значение из исходной функции.
- Это изменяет имя функции, которая может повлиять на другие декортеры/код.
Поэтому используйте @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
Ответ 5
Я предполагаю, что ваша проблема заключается в передаче аргументов вашему декоратору. Это немного сложно, а не просто.
Вот пример того, как это сделать:
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
См. Статью Брюса Экеля для более подробной информации.
Ответ 6
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
Использование декоратора
@decorator(2)
def adder(*args):
sum=0
for i in args:
sum+=i
return sum
Тогда
adder(2,3)
производит
10
но
adder('hi',3)
производит
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-143-242a8feb1cc4> in <module>
----> 1 adder('hi',3)
<ipython-input-140-d3420c248ebd> 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
Ответ 7
В моем случае я решил решить это с помощью однострочной лямбды, чтобы создать новую функцию декоратора:
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!
Возможно, не такой расширяемый, как другие решения, но работал для меня.
Ответ 8
Это шаблон для декоратора функции, который не требует ()
, если не нужно задавать никаких параметров:
import functools
def decorator(x_or_func=None, *decorator_args, **decorator_kws):
def _decorator(func):
@functools.wraps(func)
def wrapper(*args, **kws):
if 'x_or_func' not in locals() \
or callable(x_or_func) \
or x_or_func is None:
x = ... # <-- default 'x' value
else:
x = x_or_func
return func(*args, **kws)
return wrapper
return _decorator(x_or_func) if callable(x_or_func) else _decorator
пример этого приведен ниже:
def multiplying(factor_or_func=None):
def _decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if 'factor_or_func' not in locals() \
or callable(factor_or_func) \
or factor_or_func is None:
factor = 1
else:
factor = factor_or_func
return factor * func(*args, **kwargs)
return wrapper
return _decorator(factor_or_func) if callable(factor_or_func) else _decorator
@multiplying
def summing(x): return sum(x)
print(summing(range(10)))
# 45
@multiplying()
def summing(x): return sum(x)
print(summing(range(10)))
# 45
@multiplying(10)
def summing(x): return sum(x)
print(summing(range(10)))
# 450
Ответ 9
Определите эту "функцию decoratorize" для генерации настраиваемой функции decorator:
def decoratorize(FUN, **kw):
def foo(*args, **kws):
return FUN(*args, **kws, **kw)
return foo
используйте это так:
@decoratorize(FUN, arg1 = , arg2 = , ...)
def bar(...):
...