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


у меня проблема с передачей переменной '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 267

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

вот немного измененная версия ответ т. Дубровника. Зачем?

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

чтобы использовать @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!

возможно, не такой расширяемый, как другие решения, но работал для меня.