Декораторы с параметрами? - PullRequest
333 голосов
/ 08 мая 2011

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

Ответы [ 8 ]

575 голосов
/ 08 мая 2011

Вы имеете в виду def test_booking_gta_object, верно?В любом случае, синтаксис для декораторов с аргументами немного отличается - декоратор с аргументами должен возвращать функцию, которая будет принимать функцию и возвращать другую функцию.Так что действительно должен вернуть нормальный декоратор.Немного смущает, верно?Я имею в виду:

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

Здесь вы можете прочитать больше на эту тему - это также можно реализовать с помощью вызываемых объектов, что также объясняется там.

255 голосов
/ 13 сентября 2014

Редактировать : для глубокого понимания ментальной модели декораторов взгляните на этот потрясающий 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)>
72 голосов
/ 02 октября 2014

Я хотел бы показать идею, которая ИМХО довольно элегантна. Решение, предложенное 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 для функций-оболочек, но я бы рекомендовал использовать его все время.

59 голосов
/ 03 марта 2017

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

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

Так что используйте @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
36 голосов
/ 08 мая 2011

Полагаю, ваша проблема заключается в передаче аргументов вашему декоратору. Это немного сложно и не просто.

Вот пример того, как это сделать:

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

См. Статью Брюса Экеля для более подробной информации.

4 голосов
/ 30 декабря 2018
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
3 голосов
/ 15 июня 2018

В моем случае я решил решить эту проблему через однострочную лямбду, чтобы создать новую функцию декоратора:

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!

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

0 голосов
/ 30 октября 2018

определить эту «функцию декоратора» для генерации настраиваемой функции декоратора:

def decoratorize(FUN, **kw):
    def foo(*args, **kws):
        return FUN(*args, **kws, **kw)
    return foo

используйте это так:

    @decoratorize(FUN, arg1 = , arg2 = , ...)
    def bar(...):
        ...
...