1. ホーム
  2. python

[解決済み] Python クラスのすべてのメソッドを指定されたデコレータで取得する方法

2023-04-26 02:13:10

質問

与えられたクラスAで、@decorator2で装飾されたすべてのメソッドを取得するにはどうすればよいですか?

class A():
    def method_a(self):
      pass

    @decorator1
    def method_b(self, b):
      pass

    @decorator2
    def method_c(self, t=5):
      pass

どのように解決するのですか?

方法1:基本的なデコレータの登録

この質問にはすでにこちらで回答しています。 Pythonで配列のインデックスで関数を呼び出す =)


方法2:ソースコードのパース

を制御できない場合は クラス 定義 と仮定するのは一つの解釈で、これは 不可能 (code-reading-reflectionなしで)です。例えば、デコレータは単に関数を変更せずに返すだけのno-opデコレータ(私のリンク先の例のように)である可能性があるからです。(それでも、もしあなたがデコレータをラップ/再定義することを許したなら、以下を参照してください。 方法3: デコレーターを "self-aware" に変換する。 を参照してください。)

とんでもなくひどいハックですが、あなたは inspect モジュールを使ってソースコードそのものを読み、それをパースすることができます。これはインタラクティブなインタープリタでは動作しません。なぜなら、inspect モジュールはインタラクティブモードではソースコードの提供を拒否するからです。しかし、以下は概念的な証明です。

#!/usr/bin/python3

import inspect

def deco(func):
    return func

def deco2():
    def wrapper(func):
        pass
    return wrapper

class Test(object):
    @deco
    def method(self):
        pass

    @deco2()
    def method2(self):
        pass

def methodsWithDecorator(cls, decoratorName):
    sourcelines = inspect.getsourcelines(cls)[0]
    for i,line in enumerate(sourcelines):
        line = line.strip()
        if line.split('(')[0].strip() == '@'+decoratorName: # leaving a bit out
            nextLine = sourcelines[i+1]
            name = nextLine.split('def')[1].split('(')[0].strip()
            yield(name)

うまくいった!?

>>> print(list(  methodsWithDecorator(Test, 'deco')  ))
['method']

パースとpythonの構文に注意する必要があることに注意してください、例えば @deco@deco(... は有効な結果ですが @deco2 は返されないはずです。 'deco' . の公式の Python 構文によると、以下のようになります。 http://docs.python.org/reference/compound_stmts.html の公式な構文によると、デコレータは以下のようになります。

decorator      ::=  "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE

のようなケースに対処する必要がなくなり、安堵のため息をついています。 @(deco) . しかし、これでもまだ、たとえば @getDecorator(...) のような複雑なデコレータがある場合、これはまだあまり役に立ちません。

def getDecorator():
    return deco

このように、コードを解析するこの最善策では、このようなケースを検出することはできません。とはいえ、この方法を使う場合、本当に必要なのは、定義の中でメソッドの上に書かれていることで、この場合は getDecorator .

仕様によれば、以下のような記述も有効です。 @foo1.bar2.baz3(...) をデコレータとして使用することも可能です。このメソッドを拡張して、それを扱うことができます。また、このメソッドを拡張して <function object ...> を返すように拡張することもできるかもしれません。しかし、この方法はハック的でひどいものです。


方法3: デコレーターを "self-aware" に変換する。

を制御できない場合は デコレータ 定義 (これはあなたが望むことの別の解釈です) であれば、 デコレータがどのように適用されるかをあなたが制御できるので、 これらの問題はすべて解決します。このように、デコレータを修正するには を包む をラップすることで、デコレータを変更できます。 を作成します。 デコレータを作成し その を使って関数を装飾することができます。もう一度言いますが、自分がコントロールできないデコレータを装飾するデコレータを作り、それを "enlightning"して、この例では以前やっていたことをさせるのですが、しかし また を追加します。 .decorator メタデータプロパティを追加し、「この関数はデコレートされているかどうか?そして では を使えば、クラスのメソッドを繰り返し実行し、デコレータが適切な .decorator プロパティを持つかどうかを確認するだけです! =) ここで実証されているように

def makeRegisteringDecorator(foreignDecorator):
    """
        Returns a copy of foreignDecorator, which is identical in every
        way(*), except also appends a .decorator property to the callable it
        spits out.
    """
    def newDecorator(func):
        # Call to newDecorator(method)
        # Exactly like old decorator, but output keeps track of what decorated it
        R = foreignDecorator(func) # apply foreignDecorator, like call to foreignDecorator(method) would have done
        R.decorator = newDecorator # keep track of decorator
        #R.original = func         # might as well keep track of everything!
        return R

    newDecorator.__name__ = foreignDecorator.__name__
    newDecorator.__doc__ = foreignDecorator.__doc__
    # (*)We can be somewhat "hygienic", but newDecorator still isn't signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it's not a big issue

    return newDecorator

のデモ @decorator :

deco = makeRegisteringDecorator(deco)

class Test2(object):
    @deco
    def method(self):
        pass

    @deco2()
    def method2(self):
        pass

def methodsWithDecorator(cls, decorator):
    """ 
        Returns all methods in CLS with DECORATOR as the
        outermost decorator.

        DECORATOR must be a "registering decorator"; one
        can make any decorator "registering" via the
        makeRegisteringDecorator function.
    """
    for maybeDecorated in cls.__dict__.values():
        if hasattr(maybeDecorated, 'decorator'):
            if maybeDecorated.decorator == decorator:
                print(maybeDecorated)
                yield maybeDecorated

うまくいった!?

>>> print(list(   methodsWithDecorator(Test2, deco)   ))
[<function method at 0x7d62f8>]

ただし、quot;registered decorator"は、quot;registered decoratorと同じように 一番外側のデコレータ でなければならず、そうでなければ .decorator 属性のアノテーションが失われてしまいます。例えば

@decoOutermost
@deco
@decoInnermost
def func(): ...

というメタデータのみ見ることができます。 decoOutermost が公開するメタデータしか見ることができません。

補足: 上記の方法で .decorator を追跡することができます。 適用されたデコレータや入力関数、デコレータファクトリの引数のスタック全体を追跡するR.original = func@foo@bar(...)

foo