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

У меня проблема с переносом переменной "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. Зачем?

  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

Ответ 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(...):
        ...