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

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

person falek.marcin    schedule 08.05.2011    source источник
comment
Ваш пример синтаксически неверен. execute_complete_reservation принимает два параметра, но вы передаете ему один. Декораторы - это просто синтаксический сахар для обертывания функций внутри других функций. См. docs.python.org/reference/compound_stmts.html#function для полной информации. документация.   -  person Brian Clapper    schedule 08.05.2011


Ответы (17)


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

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

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

person t.dubrownik    schedule 08.05.2011
comment
Спасибо, ваше решение больше подходит для моей проблемы - Легко объясняет, как создавать декораторы с параметрами - person falek.marcin; 08.05.2011
comment
Я просто повсюду проделывал это с лямбдами. (читайте: Python потрясающий!) :) - person Alois Mahdal; 06.06.2013
comment
Интересно, почему GVR не реализовал его, передав параметры в качестве последующих аргументов декоратора после «функции». «Эй, чувак, я слышал, ты любишь закрытие ...» и так далее. - person Michel Müller; 08.04.2014
comment
@ MichelMüller, интересно! Но мне приходит в голову, что вам нужно принять соглашение. Будет function первым аргументом или последним? Я мог бы привести аргументы в пользу того и другого. Выбор кажется произвольным, и поэтому у вас будет еще одна случайная вещь, которую нужно запомнить о языке. Также странно, что вы вызываете функцию с подписью, отличной от той, что указана в определении. Я полагаю, что то же самое происходит и с методами класса, но классы - это полноценные языковые конструкции с глубокой семантикой, тогда как декораторы должны быть синтаксическим сахаром. - person senderle; 18.02.2015
comment
›Будет ли функция первым аргументом или последним? Очевидно, первое, поскольку параметры - это список параметров переменной длины. ›Также странно, что вы вызываете функцию с подписью, отличной от сигнатуры в определении. Как вы отметили, на самом деле он подошел бы очень хорошо - это в значительной степени аналогично тому, как вызывается метод класса. Чтобы было понятнее, у вас может быть что-то вроде соглашения о декораторах (self_func, param1, ...). Но обратите внимание: я не выступаю за какие-либо изменения здесь, Python слишком далеко для этого, и мы можем увидеть, как сработали критические изменения ... - person Michel Müller; 19.02.2015
comment
вы забыли ОЧЕНЬ ПОЛЕЗНЫЕ functools.wraps для украшения обертки :) - person socketpair; 14.08.2015
comment
Это работает, но у меня иногда возникают проблемы с argument, которые не видны во внутренней функции wrapper (2.7.6, версия для Apple OSX). Вы знаете, является ли эта проблема известной? - person Raffi; 12.11.2015
comment
Вы забыли про return при вызове функции, т.е. return function(*args, **kwargs) - person formiaczek; 01.12.2015
comment
Может быть, очевидно, но на всякий случай: вам нужно использовать этот декоратор как @decorator(), а не только @decorator, даже если у вас есть только необязательные аргументы. - person Patrick Mevzek; 04.12.2017
comment
Чтобы завершить это, вы также можете добавить @functools.wraps(function) над строкой def wrapper.... - person Mohammad Banisaeid; 24.12.2017
comment
Спасибо, много лет спустя, но это решило мою проблему. Наконец-то я чувствую, что понимаю идею декораторов. - person Nicholas Humphrey; 20.05.2019
comment
Хотя это решение, оно действительно не кажется элегантным или питоническим. Я всегда рассматривал декораторы как объектные методы, которые автоматически передают self в качестве аргумента при вызове. Наличие дополнительного функционального уровня для обработки аргументов кажется неправильным - person Tian; 02.06.2020
comment
@Titan Я бы сказал, что это очень питонин. Вложенные методы позволяют определять декораторы отдельно от их использования. Это позволяет использовать два шага x=decorator_factory(foo) @x; def ..., а также более общий одиночный шаг @decorator_factory(foo); def .... Хотя это менее распространено, но используется и очень полезно. - person Philip Couling; 30.01.2021
comment
Это помогает мне понять, как Flask @ app.route (путь) сопоставляет или вызывает его функцию просмотра. Спасибо. - person Leon Chang; 22.04.2021

Изменить: для более глубокого понимания ментальной модели декораторов взгляните на this Замечательный 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):
        # 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
person srj    schedule 13.09.2014
comment
Ваш ответ прекрасно объяснил неотъемлемую ортогональность декоратора, спасибо - person zsf222; 09.12.2017
comment
Не могли бы вы добавить @functools.wraps? - person Mr_and_Mrs_D; 26.08.2018
comment
@Mr_and_Mrs_D, я обновил пост примером с functool.wraps. Добавление его в пример может еще больше запутать читателей. - person srj; 28.08.2018
comment
Что здесь arg !? - person Stefan Falk; 25.09.2018
comment
@StefanFalk arg - это просто имя переменной со значением, которое вы использовали бы для создания real_decorator из _pseudo_decor - person srj; 26.09.2018
comment
Как передать аргумент, переданный bar аргументу real_decorator? - person Chang Zhao; 01.11.2018
comment
@ChangZhao, как описано выше, даже если вы используете имя bar в коде после объявления, python указывает имя bar на результат real_decorator(bar), поэтому любые аргументы, которые вы передаете при вызове bar(arg1, arg2..), фактически интерпретируются как real_decorator(bar)(arg1, arg2,..) - person srj; 01.11.2018
comment
как это будет работать с частичным? потому что вы передаете arg! - person Chang Zhao; 01.11.2018
comment
Попробуй сам. надеюсь, этот фрагмент кода не требует пояснений. repl.it/@sreedom/SpotlessTastyScreencast - person srj; 04.11.2018
comment
Самое красивое объяснение, которое я когда-либо видел! - person Cho; 25.07.2019
comment
Спасибо за это объяснение. Ссылка на разговор о pycon тоже очень помогла. - person Radha Satam; 13.03.2020
comment
Прекрасное объяснение. Это то, что ищет каждый, кто изучает декораторы Python. - person Ahmed Gad; 26.04.2020
comment
Так как же это будет работать, если вы не знаете arg, пока не пришло время фактически запустить функцию? иначе, вы хотите окружить строку тегом HTML, но используемый тег может каждый раз отличаться (или даже определяться пользователем)? - person hasdrubal; 25.08.2020
comment
Видео было отличным. Спасибо! - person Lee Loftiss; 12.09.2020
comment
В numba библиотеке есть @jit(nopython=True) выражение. Тогда как такое возможно? В этом сценарии я не понимаю, как numba случайно получает аргумент. - person user8491363; 21.12.2020
comment
Я отредактировал пример с wraps, чтобы было понятнее использование arg. - person srj; 21.01.2021

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

person Dacav    schedule 01.10.2014
comment
Точно не использовал, но помог мне разобраться в концепции :) Спасибо! - person mouckatron; 11.10.2017
comment
Я попробовал это, и у меня возникли проблемы. - person Jeff; 14.10.2017
comment
@ Джефф, не могли бы вы поделиться с нами своими проблемами? - person Dacav; 14.10.2017
comment
У меня была ссылка на мой вопрос, и я понял это ... Мне нужно было позвонить @wraps в моем для моего конкретного случая. - person Jeff; 14.10.2017
comment
Ага! Я подозревал, что это была единственная возможная ошибка, которую я мог заметить. На самом деле я изменил свой ответ и добавил упоминание о wraps. Правдивая история :) - person Dacav; 16.10.2017
comment
Это работает, но я не могу понять это. Как в этом случае можно было бы преобразовать синтаксический сахар декораторов в обыденные вызовы? Продолжая ваш пример, будет ли это: function = parametrized(multiply(function, 2)), а затем вызов function(3), чтобы получить 26? Не получается, когда пробую с аналогами - person z33k; 10.03.2018
comment
@ o'rety Меня немного смутил твой вопрос. Не могли бы вы уточнить? - person Dacav; 13.03.2018
comment
О боже, я потерял на этом целый день. К счастью, я нашел этот ответ (который, кстати, может быть лучшим ответом, когда-либо созданным во всем Интернете). Они тоже используют ваш @parametrized трюк. Проблема, с которой я столкнулся, заключалась в том, что я забыл @ синтаксис равно фактическим вызовам (почему-то я знал это и не знал этого в то же время, как вы можете понять из моего вопроса). Поэтому, если вы хотите перевести синтаксис @ в обычные вызовы, чтобы проверить, как он работает, вам лучше сначала его временно прокомментировать, иначе вы в конечном итоге вызовете его дважды и получите бессмысленные результаты. - person z33k; 13.03.2018
comment
С одной стороны, это круто, но я не понимаю, почему 3 метода инкапсулируются друг с другом. А именно, я не понимаю, что вообще делает repl (), это просто трюк с областями видимости и числами параметров? - person Gyula Sámuel Karli; 24.11.2018
comment
repl означает замену. Когда я назову его repl, это означает, что он фактически заменит другую функцию. Предполагая, что вы говорите об parametrized декораторе, layer заменит декорированный декоратор. Параметр dec в parametrized - это декоратор, который мы собираемся заменить на layer. Декораторы принимают функции в качестве параметра, и мы действительно передаем f в dec. Итак, в конечном итоге repl - это то, что заменяет dec, и работает, применяя декоратор dec так, чтобы он заменял f. Я надеюсь, что этот комментарий проясняет ситуацию, но мне самому приходится читать его много раз. - person Dacav; 24.11.2018

Вот немного измененная версия ответа 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
person Ross R    schedule 03.03.2017
comment
Я так и сделал, но на лямбдах AWS с колбой это не работает: python 3.8 возвращает эту ошибку: AssertionError: View function mapping is overwriting an existing endpoint function: authorization_required_wrapper - person Lucas Andrade; 10.05.2021

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

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

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

Дополнительные сведения см. в статье Брюса Эккеля.

person Ross Rogers    schedule 08.05.2011
comment
Остерегайтесь классов-декораторов. Они не работают с методами, если вы вручную не изобретете логику дескрипторов метода экземпляра. - person ; 08.05.2011
comment
delnan, уточните подробнее? Мне пришлось использовать этот шаблон только один раз, так что я еще не наткнулся ни на одну из ловушек. - person Ross Rogers; 08.05.2011
comment
@RossRogers Я предполагаю, что @delnan имеет в виду такие вещи, как __name__, которых не будет у экземпляра класса декоратора? - person jamesc; 13.01.2014
comment
@jamesc И это тоже, хотя это относительно легко решить. Конкретный случай, о котором я говорил, был class Foo: @MyDec(...) def method(self, ...): blah, который не работает, потому что Foo().method не будет связанным методом и не будет передавать self автоматически. Это тоже можно исправить, сделав MyDec дескриптор и создав связанные методы в __get__, но это более сложно и менее очевидно. В конце концов, классы-декораторы не так удобны, как кажутся. - person ; 14.01.2014
comment
@delnan Я бы хотел, чтобы это предупреждение было более заметным. Я нажимаю на него, и мне интересно увидеть решение, которое ДЕЙСТВИТЕЛЬНО работает (более сложное, но менее очевидное, хотя оно может быть). - person HaPsantran; 13.03.2016
comment
Это кажется отличным ответом, но я не понимаю некоторых шагов. Зачем копировать себя в decorator_self. Чтобы обойти проблему, упомянутую выше? - person Stephen Ellwood; 01.10.2019
comment
Я не думаю, что decorator_self больше нужен. Это было, потому что когда я экспериментировал, у меня был self в качестве первого параметра wrapee. Попробуйте без него, просто используя self и не применяя псевдоним self к decorator_self - person Ross Rogers; 01.10.2019

Написать декоратор, который работает с параметром и без него, является сложной задачей, потому что Python ожидает совершенно разного поведения в этих двух случаях! Многие ответы пытались обойти это, и ниже приведено улучшение ответа @ norok2. В частности, этот вариант исключает использование locals().

Следуя тому же примеру, что и @ norok2:

import functools

def multiplying(f_py=None, factor=1):
    assert callable(f_py) or f_py is None
    def _decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return factor * func(*args, **kwargs)
        return wrapper
    return _decorator(f_py) if callable(f_py) 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(factor=10)
def summing(x): return sum(x)

print(summing(range(10)))
# 450

Поиграйте с этим кодом.

Загвоздка в том, что пользователь должен указать пары параметров ключ, значение вместо позиционных параметров, а первый параметр зарезервирован.

person Shital Shah    schedule 24.03.2020
comment
Это гребаный гений. - person medley56; 14.08.2020

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
person Gajendra D Ambi    schedule 29.12.2018
comment
Из всех сообщений здесь этот ответ оказался наиболее полезным для моего понимания того, как передается и обрабатывается аргумент. - person sc_props; 23.11.2020

Это шаблон для декоратора функции, который не требует (), если не нужно указывать параметры:

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
person norok2    schedule 30.07.2019
comment
Также обратите внимание, что factor_or_func (или любой другой параметр) никогда не должен переназначаться в wrapper(). - person norok2; 28.08.2019
comment
Зачем вам нужно регистрироваться locals()? - person Shital Shah; 24.03.2020
comment
@ShitalShah, который охватывает случай, когда декоратор используется без (). - person norok2; 24.03.2020

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

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!

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

person ZacBook    schedule 15.06.2018
comment
Это работает. Хотя да, это затрудняет установку значения для декоратора. - person Arindam Roychowdhury; 11.01.2019

введите описание изображения здесь

  • Здесь мы дважды запускали отображение информации с двумя разными именами и двумя разными возрастами.
  • Теперь каждый раз, когда мы запускали отображение информации, наши декораторы также добавляли функцию печати строки до и строки после этой обернутой функции.
def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        print('Executed Before', original_function.__name__)
        result = original_function(*args, **kwargs)
        print('Executed After', original_function.__name__, '\n')
        return result
    return wrapper_function


@decorator_function
def display_info(name, age):
    print('display_info ran with arguments ({}, {})'.format(name, age))


display_info('Mr Bean', 66)
display_info('MC Jordan', 57)

выход:

Executed Before display_info
display_info ran with arguments (Mr Bean, 66)
Executed After display_info 

Executed Before display_info
display_info ran with arguments (MC Jordan, 57)
Executed After display_info 
  • Итак, теперь давайте продолжим и заставим нашу функцию-декоратор принимать аргументы.

  • Например, предположим, что мне нужен настраиваемый префикс для всех этих операторов печати внутри оболочки.

  • Теперь это может быть хорошим аргументом в пользу декоратора.

  • Аргумент, который мы передаем, будет этим префиксом. Теперь, чтобы сделать это, мы просто собираемся добавить еще один внешний слой к нашему декоратору, поэтому я собираюсь называть эту функцию декоратором префикса.

def prefix_decorator(prefix):
    def decorator_function(original_function):
        def wrapper_function(*args, **kwargs):
            print(prefix, 'Executed Before', original_function.__name__)
            result = original_function(*args, **kwargs)
            print(prefix, 'Executed After', original_function.__name__, '\n')
            return result
        return wrapper_function
    return decorator_function


@prefix_decorator('LOG:')
def display_info(name, age):
    print('display_info ran with arguments ({}, {})'.format(name, age))


display_info('Mr Bean', 66)
display_info('MC Jordan', 57)

выход:

LOG: Executed Before display_info
display_info ran with arguments (Mr Bean, 66)
LOG: Executed After display_info 

LOG: Executed Before display_info
display_info ran with arguments (MC Jordan, 57)
LOG: Executed After display_info 
  • Теперь у нас есть префикс LOG: перед операторами печати в нашей функции-оболочке, и вы можете изменить его в любое время, когда захотите.
person Milovan Tomašević    schedule 10.01.2021

Вот так просто

def real_decorator(any_number_of_arguments):
   def pseudo_decorator(function_to_be_decorated):

       def real_wrapper(function_arguments):
           print(function_arguments)
           result = function_to_be_decorated(any_number_of_arguments)
           return result

       return real_wrapper
   return pseudo_decorator

Сейчас

@real_decorator(any_number_of_arguments)
def some_function(function_arguments):
        return "Any"
person Henshal B    schedule 03.03.2021

Хорошо известно, что следующие два фрагмента кода почти эквивалентны:

@dec
def foo():
    pass    foo = dec(foo)

############################################
foo = dec(foo)

Распространенная ошибка - думать, что @ просто скрывает самый левый аргумент.

@dec(1, 2, 3)
def foo():
    pass    
###########################################
foo = dec(foo, 1, 2, 3)

Было бы намного проще писать декораторы, если бы все это работало так, как @. К сожалению, это не так.


Рассмотрим декоратор Wait, который на несколько секунд прерывает выполнение программы. Если вы не пропустите время ожидания, значение по умолчанию - 1 секунда. Примеры использования показаны ниже.

##################################################
@Wait
def print_something(something):
    print(something)

##################################################
@Wait(3)
def print_something_else(something_else):
    print(something_else)

##################################################
@Wait(delay=3)
def print_something_else(something_else):
    print(something_else)

Когда Wait имеет аргумент, например @Wait(3), тогда вызов Wait(3) выполняется до, что произойдет еще.

То есть следующие два фрагмента кода эквивалентны

@Wait(3)
def print_something_else(something_else):
    print(something_else)

###############################################
return_value = Wait(3)
@return_value
def print_something_else(something_else):
    print(something_else)

Это проблема.

if `Wait` has no arguments:
    `Wait` is the decorator.
else: # `Wait` receives arguments
    `Wait` is not the decorator itself.
    Instead, `Wait` ***returns*** the decorator

Одно из решений показано ниже:

Начнем с создания следующего класса DelayedDecorator:

class DelayedDecorator:
    def __init__(i, cls, *args, **kwargs):
        print("Delayed Decorator __init__", cls, args, kwargs)
        i._cls = cls
        i._args = args
        i._kwargs = kwargs
    def __call__(i, func):
        print("Delayed Decorator __call__", func)
        if not (callable(func)):
            import io
            with io.StringIO() as ss:
                print(
                    "If only one input, input must be callable",
                    "Instead, received:",
                    repr(func),
                    sep="\n",
                    file=ss
                )
                msg = ss.getvalue()
            raise TypeError(msg)
        return i._cls(func, *i._args, **i._kwargs)

Теперь мы можем писать такие вещи, как:

 dec = DelayedDecorator(Wait, delay=4)
 @dec
 def delayed_print(something):
    print(something)

Обратите внимание, что:

  • dec не принимает несколько аргументов.
  • dec принимает только функцию, которую нужно обернуть.

    import inspect class PolyArgDecoratorMeta (type): def call (Wait, * args, ** kwargs): try: arg_count = len (args) if (arg_count == 1): if callable (args [0 ]): SuperClass = inspect.getmro (PolyArgDecoratorMeta) [1] r = SuperClass. call (Wait, args [0]) else: r = DelayedDecorator (Wait, * args, ** kwargs) else : r = DelayedDecorator (Подождите, * args, ** kwargs) наконец: пройти возврат r

    класс времени импорта Wait (metaclass = PolyArgDecoratorMeta): def init (i, func, delay = 2): i._func = func i._delay = delay

    def __call__(i, *args, **kwargs):
        time.sleep(i._delay)
        r = i._func(*args, **kwargs)
        return r 
    

Следующие два фрагмента кода эквивалентны:

@Wait
def print_something(something):
     print (something)

##################################################

def print_something(something):
    print(something)
print_something = Wait(print_something)

Мы можем печатать "something" на консоль очень медленно, как показано ниже:

print_something("something")

#################################################
@Wait(delay=1)
def print_something_else(something_else):
    print(something_else)

##################################################
def print_something_else(something_else):
    print(something_else)

dd = DelayedDecorator(Wait, delay=1)
print_something_else = dd(print_something_else)

##################################################

print_something_else("something")

Заключительные примечания

Это может показаться большим количеством кода, но вам не нужно писать классы DelayedDecorator и PolyArgDecoratorMeta каждый раз. Единственный код, который вам нужно лично написать, выглядит примерно так, довольно кратко:

from PolyArgDecoratorMeta import PolyArgDecoratorMeta
import time
class Wait(metaclass=PolyArgDecoratorMeta):
 def __init__(i, func, delay = 2):
     i._func = func
     i._delay = delay

 def __call__(i, *args, **kwargs):
     time.sleep(i._delay)
     r = i._func(*args, **kwargs)
     return r
person Samuel Muldoon    schedule 30.10.2019

Отличные ответы выше. Этот также иллюстрирует @wraps, который берет строку документа и имя функции из исходной функции и применяет их к новой обернутой версии:

from functools import wraps

def decorator_func_with_args(arg1, arg2):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            print("Before orginal function with decorator args:", arg1, arg2)
            result = f(*args, **kwargs)
            print("Ran after the orginal function")
            return result
        return wrapper
    return decorator

@decorator_func_with_args("foo", "bar")
def hello(name):
    """A function which prints a greeting to the name provided.
    """
    print('hello ', name)
    return 42

print("Starting script..")
x = hello('Bob')
print("The value of x is:", x)
print("The wrapped functions docstring is:", hello.__doc__)
print("The wrapped functions name is:", hello.__name__)

Печать:

Starting script..
Before orginal function with decorator args: foo bar
hello  Bob
Ran after the orginal function
The value of x is: 42
The wrapped functions docstring is: A function which prints a greeting to the name provided.
The wrapped functions name is: hello
person run_the_race    schedule 18.03.2020

определите эту "функцию декоратора" для создания настраиваемой функции декоратора:

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

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

    @decoratorize(FUN, arg1 = , arg2 = , ...)
    def bar(...):
        ...
person chen.wq    schedule 30.10.2018

Вот пример Flask с использованием декораторов с параметрами. Предположим, у нас есть маршрут «/ пользователь / имя», и мы хотим сопоставить его с его домашней страницей.

def matchR(dirPath):
    def decorator(func):
        def wrapper(msg):
            if dirPath[0:6] == '/user/':
                print(f"User route '{dirPath}' match, calling func {func}")
                name = dirPath[6:]
                return func(msg2=name, msg3=msg)
            else:
                print(f"Input dirPath '{dirPath}' does not match route '/user/'")
                return
        return  wrapper
    return decorator

#@matchR('/Morgan_Hills')
@matchR('/user/Morgan_Hills')
def home(**kwMsgs):
    for arg in kwMsgs:
        if arg == 'msg2':
            print(f"In home({arg}): Hello {kwMsgs[arg]}, welcome home!")
        if arg == 'msg3':
            print(f"In home({arg}): {kwMsgs[arg]}")

home('This is your profile rendered as in index.html.')

Выход:

User route '/user/Morgan_Hills' match, calling func <function home at 0x000001DD5FDCD310>
In home(msg2): Hello Morgan_Hills, welcome home!
In home(msg3): This is your profile rendered as in index.html.
person Leon Chang    schedule 26.04.2021

Это декоратор, который можно вызывать разными способами (проверено в python3.7):

import functools


def my_decorator(*args_or_func, **decorator_kwargs):

    def _decorator(func):

        @functools.wraps(func)
        def wrapper(*args, **kwargs):

            if not args_or_func or callable(args_or_func[0]):
                # Here you can set default values for positional arguments
                decorator_args = ()
            else:
                decorator_args = args_or_func

            print(
                "Available inside the wrapper:",
                decorator_args, decorator_kwargs
            )

            # ...
            result = func(*args, **kwargs)
            # ...

            return result

        return wrapper

    return _decorator(args_or_func[0]) \
        if args_or_func and callable(args_or_func[0]) else _decorator


@my_decorator
def func_1(arg): print(arg)

func_1("test")
# Available inside the wrapper: () {}
# test


@my_decorator()
def func_2(arg): print(arg)

func_2("test")
# Available inside the wrapper: () {}
# test


@my_decorator("any arg")
def func_3(arg): print(arg)

func_3("test")
# Available inside the wrapper: ('any arg',) {}
# test


@my_decorator("arg_1", 2, [3, 4, 5], kwarg_1=1, kwarg_2="2")
def func_4(arg): print(arg)

func_4("test")
# Available inside the wrapper: ('arg_1', 2, [3, 4, 5]) {'kwarg_1': 1, 'kwarg_2': '2'}
# test

PS спасибо пользователю @ norok2 - https://stackoverflow.com/a/57268935/5353484

UPD Декоратор для проверки аргументов и / или результатов функций и методов класса по аннотациям. Может использоваться в синхронной или асинхронной версии: https://github.com/EvgeniyBurdin/valdec

person Evgeniy_Burdin    schedule 30.12.2020

Если и функция, и декоратор должны принимать аргументы, вы можете использовать следующий подход.

Например, есть декоратор с именем decorator1, который принимает аргумент

@decorator1(5)
def func1(arg1, arg2):
    print (arg1, arg2)

func1(1, 2)

Теперь, если аргумент decorator1 должен быть динамическим или передаваться при вызове функции,

def func1(arg1, arg2):
    print (arg1, arg2)


a = 1
b = 2
seconds = 10

decorator1(seconds)(func1)(a, b)

В приведенном выше коде

  • seconds - аргумент для decorator1
  • a, b - аргументы func1
person SuperNova    schedule 29.05.2020