Вопрос-Ответ

Decorators with parameters?

Декораторы с параметрами?

У меня проблема с передачей переменной insurance_mode декоратором. Я бы сделал это с помощью следующего оператора decorator:

@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. оно того стоит, потраченные 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 предоставляет нам удобный метод для "поднятия" docstring и name возвращаемой функции.

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
Ответ 3

Вот слегка измененная версия ответа т.дубровника. Почему?


  1. В качестве общего шаблона вы должны возвращать возвращаемое значение из исходной функции.

  2. Это изменяет имя функции, что может повлиять на другие декораторы / код.

Так что используйте @functools.wraps():

from functools import wraps

def create_decorator(argument):
def 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 decorator
Ответ 4

Я хотел бы показать идею, которая, ИМХО, довольно элегантна. Решение, предложенное т.дубровником, показывает шаблон, который всегда один и тот же: вам нужна трехслойная оболочка независимо от того, что делает декоратор.

Итак, я подумал, что это работа для метадекоратора, то есть декоратора для декораторов. Поскольку декоратор - это функция, он фактически работает как обычный декоратор с аргументами:

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 для функций-оболочек, но я бы рекомендовал использовать его постоянно.

python function