パラメータ付きのデコレータ?


401

デコレータによる変数「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

3
あなたの例は構文的に有効ではありません。execute_complete_reservation2つのパラメーターを受け取りますが、1つ渡します。デコレータは、他の関数内で関数をラップするための単なる構文上の砂糖です。詳細なドキュメントについては、docs.python.org / reference / compound_stmts.html#functionをご覧ください。
ブライアンクラッパー

回答:


687

引数付きデコレータの構文は少し異なります。引数付きデコレータは、関数を取得して別の関数を返す関数を返す必要があります。したがって、通常のデコレータを返す必要があります。少しわかりにくいですよね?つまり、

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

ここであなたは主題についてもっと読むことができます-呼び出し可能なオブジェクトを使用してこれを実装することも可能であり、それもそこで説明されています。


56
「関数」の後に後続のデコレーター引数としてパラメーターを渡すことによって、GVRがそれを実装しなかったのはなぜでしょうか。「ヨー・ダグ、あなたは閉鎖が好きだと聞いた...」など。
ミシェル・ミューラー

3
>関数は最初の引数ですか、最後ですか?明らかに最初に、パラメーターは可変長のパラメーターリストであるためです。>また、定義内のシグネチャとは異なるシグネチャで関数を「呼び出す」のもおかしいです。あなたが指摘するように、それは実際にはかなりうまく収まるでしょう-それはクラスメソッドが呼ばれる方法にかなり類似しています。より明確にするために、decorator(self_func、param1、...)規則のようなものを持つことができます。しかし、ノートでは:私はここにすべての変更を提唱ていないよ、Pythonはそのための道すぎであり、我々は重大な変更が出て働いているかを確認することができます。..
ミシェル・ミューラー

21
あなたはラッパーを飾るための非常に便利なfunctools.wrapsを忘れました:)
socketpair

10
関数を呼び出すときに戻ることを忘れた、つまり return function(*args, **kwargs)
formiaczek

36
多分明白ですが、念のため:オプションの引数しかなくても、このデコレータをとして@decorator()ではなくとして使用する必要があり@decoratorます。
Patrick Mevzek 2017

326

編集:デコレータのメンタルモデルを深く理解するには、この素晴らしいPyconトークをご覧ください。30分の価値があります。

引数を持つデコレータについて考える1つの方法は、

@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)

関数を修飾する1つの効果は、名前fooが修飾子の宣言でオーバーライドされることです。fooによって返されるものによって「オーバーライド」されreal_decoratorます。この場合、新しい関数オブジェクトです。

のすべてfooのメタデータ、特にdocstringと関数名はオーバーライドされます。

>>> 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)>

4
あなたの答えは、デコレータの固有の直交性を完全に説明しました。ありがとう
zsf222

追加してもらえます@functools.wrapsか?
Mr_and_Mrs_D

1
@Mr_and_Mrs_D、私はの例で投稿を更新しましたfunctool.wraps。例に追加すると、読者をさらに混乱させる可能性があります。
srj 2018

7
argここは何!?
displayname

1
の引数に渡されbarた引数をどのように渡しますreal_decoratorか?
Chang Zhao

85

とてもエレガントなIMHOのアイデアを紹介します。t.dubrownikによって提案されたソリューションは、常に同じパターンを示しています。デコレータの動作に関係なく、3層のラッパーが必要です。

だから、これはメタデコレータ、つまりデコレータのデコレータの仕事だと思いました。デコレータは関数であるため、実際には引数を持つ通常のデコレータとして機能します。

def parametrized(dec):
    def layer(*args, **kwargs):
        def repl(f):
            return dec(f, *args, **kwargs)
        return repl
    return layer

これは、パラメータを追加するために通常のデコレータに適用できます。たとえば、関数の結果を2倍にするデコレータがあるとします。

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ではラッパー関数を使用していませんが、常に使用することをお勧めします。


3
これは正確には使用しませんでしたが、コンセプトの理解に役立ちました:)ありがとうございます。
mouckatron 2017年

私はこれを試し、いくつかの問題がありました
Jeff

@ジェフはあなたが抱えていた問題の種類を私たちと共有できますか?
Dacav

私はそれを私の質問にリンクさせました、そして私はそれを理解しました...私@wrapsは私の特定のケースのために私のものを呼ぶ必要がありました。
Jeff

4
おお、私はこれで丸一日負けた。ありがたいことに、私はこの答えに出くわしました(偶然にも、インターネット全体でこれまで作成された中で最高の答えになる可能性があります)。彼らもあなたの@parametrizedトリックを使います。私が抱えていた問題は、@構文が実際の呼び出しに等しいことを忘れていたということでした(どういうわけか私はそれを知っていて、同時に私の質問から収集できることを知りませんでした)。したがって、@構文を平凡な呼び出しに変換して動作を確認する場合は、最初に一時的にコメント化するか、2回呼び出してmumbojumboの結果を取得することをお勧めします
z33k

79

これは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

37

あなたの問題はあなたのデコレータに引数を渡すことだと思います。これは少しトリッキーで簡単ではありません。

これを行う方法の例を次に示します。

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

詳細については、Bruce Eckelの記事を参照してください。


20
デコレータクラスに注意してください。instancemethod記述子のロジックを手動で再発明しない限り、メソッドで機能しません。

9
デルナン、詳しく説明しますか?このパターンを使用したのは1回だけなので、まだ落とし穴はありません。
ロスロジャース、

2
@RossRogers私の推測では、@ delnanは__name__、デコレータクラスのインスタンスにはないものを参照しているのでしょうか。
jamesc 14年

9
@jamescそれも比較的簡単に解決できます。私が言及しclass Foo: @MyDec(...) def method(self, ...): blahていた特定のケースはFoo().method、バインドされたメソッドではなく、self自動的に渡されないため機能しないケースでした。これもMyDec、記述子を作成し、バインドされたメソッドをで作成することで修正できますが、__get__より複雑でわかりにくいものです。結局のところ、デコレータクラスは見た目ほど便利ではありません。

2
@delnanこの警告がより目立つようにしてほしい。私はそれを打っていて、機能する解決策を見ることに興味があります(それは可能性がありますが、より複雑ではありません)。
HaPsantran 2016年

12
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

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

ことにも注意してくださいfactor_or_func(または他のパラメータ)再割り当てされることは決してありませんなければなりませんwrapper()
norok2

なぜチェックインする必要があるのlocals()ですか?
Shital Shah

@ShitalShahは、デコレータを使用せずに使用する場合をカバーし()ます。
norok2

4

私のインスタンスでは、1行のラムダを使用してこれを解決し、新しいデコレータ関数を作成することにしました。

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!

おそらく他のソリューションほど拡張性はありませんが、私にとってはうまくいきました。


これは機能します。はい、ただし、これはデコレータに値を設定することを難しくします。
Arindam Roychowdhury

3

Pythonはこれら2つのケースで完全に異なる動作を期待するため、パラメーターの有無にかかわらず動作するデコレーターを作成することは困難です。多くの回答がこれを回避しようとしましたが、以下は@ 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

このコードで遊んでください

問題は、ユーザーが位置パラメーターの代わりにパラメーターのキーと値のペアを指定する必要があり、最初のパラメーターが予約されていることです。


2

次の2つのコードはほぼ同等であることはよく知られています。

@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) は他何が起こる前に実行されます。

つまり、次の2つのコードは同等です。

@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

1つのソリューションを以下に示します。

まず、次のクラスを作成します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 ラップされる関数のみを受け入れます。

    インポート検査クラス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(Wait、* args、** kwargs)最後に:pass return 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 

次の2つのコードは同等です。

@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")

最終メモ

たくさんのコードのように見えるかもしれませんが、クラスDelayedDecoratorPolyArgDecoratorMeta毎回書く必要はありません。あなたが個人的に以下のようなものを書く必要がある唯一のコードは、かなり短いです:

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

1

この「デコレータ関数」を定義して、カスタマイズされたデコレータ関数を生成します。

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

次のように使用します。

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

1

上記の素晴らしい答え。これもを示して@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

0

関数とデコレータの両方が引数を取る必要がある場合は、以下のアプローチに従うことができます。

たとえば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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.