web-dev-qa-db-ja.com

特定のデコレータを持つpythonクラスのすべてのメソッドを取得する方法

@ decorator2で装飾された特定のクラスAのすべてのメソッドを取得する方法は?

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

    @decorator1
    def method_b(self, b):
      pass

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

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

私はすでにこの質問に答えました: Pythonの配列インデックスによる関数の呼び出し =)


方法2:ソースコードの解析

class定義を制御できない場合(これは、想定したいことの1つの解釈であり、これはimpossible(code-reading-reflectionなし)。たとえば、デコレータは、変更されていない関数を返すだけのno-opデコレータ(リンクされた例のように)になる可能性があるためです。 (それでも、デコレータのラップ/再定義を許可する場合は、を参照してください。方法3:デコレータを「自己認識」に変換する、その後、エレガントなソリューションが見つかります)

これはひどいひどいハックですが、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)のようなケースに対処する必要がないことに安reliefのため息をつきます。ただし、@getDecorator(...)などの本当に複雑なデコレータがある場合、これはまだ役に立たないことに注意してください。

_def getDecorator():
    return deco
_

したがって、コードを解析するこの最善の方法では、このようなケースを検出できません。ただし、このメソッドを使用している場合、実際に必要なのは、定義のメソッドの上に書かれているものです。この場合はgetDecoratorです。

仕様によると、@foo1.bar2.baz3(...)をデコレーターとして使用することも有効です。このメソッドを拡張して、それを使用できます。また、このメソッドを拡張して、関数名ではなく_<function object ...>_を返すこともできます。しかし、この方法はハック的でひどいものです。


方法3:デコレータを「自己認識」に変換する

decorator定義を制御できない場合(これはあなたが望むものの別の解釈です)、これらの問題はすべて消えますデコレータの適用方法を制御できます。したがって、wrapping itでデコレータを変更し、ownデコレータを作成し、thatを使用して関数をデコレートします。もう一度言います。あなたが制御できないデコレータを飾るデコレータを作成し、それを「啓発」することができます。この場合、それは以前の動作を行いますが、also append a _.decorator_が返す呼び出し可能オブジェクトのメタデータプロパティ。「この関数は装飾されているかどうか?function.decoratorをチェックしましょう!」を追跡できます。そしてthenクラスのメソッドを反復処理し、デコレータに適切な_.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.__= 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>]
_

ただし、「登録済みのデコレータ」は最も外側のデコレータでなければなりません。そうでない場合、_.decorator_属性アノテーションは失われます。たとえば、電車の中で

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

「より内側の」ラッパーへの参照を保持しない限り、decoOutermostが公開するメタデータのみを表示できます。

サイドノート:上記のメソッドは、適用されたデコレータと入力関数とデコレータファクトリ引数のスタック全体を追跡する_.decorator_を構築することもできます。 =)たとえば、コメントアウトされた行_R.original = func_を考慮する場合、このようなメソッドを使用してすべてのラッパーレイヤーを追跡することが可能です。これは、個人的にデコレータライブラリを作成した場合に行うことです。なぜなら、詳細なイントロスペクションが可能になるからです。

_@foo_と@bar(...)にも違いがあります。どちらも仕様で定義されている「装飾表現」ですが、fooは装飾子であり、bar(...)は動的に作成された装飾子を返し、それが適用されることに注意してください。したがって、別の関数makeRegisteringDecoratorFactoryが必要になります。これはmakeRegisteringDecoratorに似ていますが、さらにメタです。

_def makeRegisteringDecoratorFactory(foreignDecoratorFactory):
    def newDecoratorFactory(*args, **kw):
        oldGeneratedDecorator = foreignDecoratorFactory(*args, **kw)
        def newGeneratedDecorator(func):
            modifiedFunc = oldGeneratedDecorator(func)
            modifiedFunc.decorator = newDecoratorFactory # keep track of decorator
            return modifiedFunc
        return newGeneratedDecorator
    newDecoratorFactory.__= foreignDecoratorFactory.__name__
    newDecoratorFactory.__doc__ = foreignDecoratorFactory.__doc__
    return newDecoratorFactory
_

@decorator(...)のデモ:

_def deco2():
    def simpleDeco(func):
        return func
    return simpleDeco

deco2 = makeRegisteringDecoratorFactory(deco2)

print(deco2.__name__)
# RESULT: 'deco2'

@deco2()
def f():
    pass
_

このジェネレーターファクトリラッパーも機能します。

_>>> print(f.decorator)
<function deco2 at 0x6a6408>
_

bonus方法#3で以下を試してみましょう:

_def getDecorator(): # let's do some dispatching!
    return deco

class Test3(object):
    @getDecorator()
    def method(self):
        pass

    @deco2()
    def method2(self):
        pass
_

結果:

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

ご覧のとおり、method2とは異なり、@ decoはクラスで明示的に記述されていなくても正しく認識されます。 method2とは異なり、メソッドが実行時に(手動で、メタクラスなどを介して)追加されるか、継承される場合にも機能します。

クラスを装飾することもできるので、メソッドとクラスの両方を装飾するために使用されるデコレータを「啓発」し、クラスを記述する場合は注意してください分析するクラスの本体内 、次にmethodsWithDecoratorは装飾されたクラスと装飾されたメソッドを返します。これを機能と見なすこともできますが、デコレータへの引数、つまり_.original_を調べて目的のセマンティクスを実現することで、それらを無視するロジックを簡単に作成できます。

105
ninjagecko

方法2:ソースコードの解析で@ninjageckoの優れた答えを拡張するには、Python 2.6で導入されたastモジュールを使用して、検査モジュールがソースコードにアクセスできます。

def findDecorators(target):
    import ast, inspect
    res = {}
    def visit_FunctionDef(node):
        res[node.name] = [ast.dump(e) for e in node.decorator_list]

    V = ast.NodeVisitor()
    V.visit_FunctionDef = visit_FunctionDef
    V.visit(compile(inspect.getsource(target), '?', 'exec', ast.PyCF_ONLY_AST))
    return res

少し複雑な装飾メソッドを追加しました。

@x.y.decorator2
def method_d(self, t=5): pass

結果:

> findDecorators(A)
{'method_a': [],
 'method_b': ["Name(id='decorator1', ctx=Load())"],
 'method_c': ["Name(id='decorator2', ctx=Load())"],
 'method_d': ["Attribute(value=Attribute(value=Name(id='x', ctx=Load()), attr='y', ctx=Load()), attr='decorator2', ctx=Load())"]}
14
Shane Holloway

たぶん、デコレータがあまり複雑ではない場合(しかし、あまりハッキングの方法があるかどうかはわかりません)。

def decorator1(f):
    def new_f():
        print "Entering decorator1", f.__name__
        f()
    new_f.__= f.__name__
    return new_f

def decorator2(f):
    def new_f():
        print "Entering decorator2", f.__name__
        f()
    new_f.__= f.__name__
    return new_f


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

    @decorator1
    def method_b(self, b):
      pass

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

print A.method_a.im_func.func_code.co_firstlineno
print A.method_b.im_func.func_code.co_firstlineno
print A.method_c.im_func.func_code.co_firstlineno
0
user227667

NinjageckoのMethod 2の単純なバリエーションである多くを追加したくありません。

同じコードですが、ジェネレーターの代わりにリスト内包表記を使用しています。これが必要なことです。

def methodsWithDecorator(cls, decoratorName):

    sourcelines = inspect.getsourcelines(cls)[0]
    return [ sourcelines[i+1].split('def')[1].split('(')[0].strip()
                    for i, line in enumerate(sourcelines)
                    if line.split('(')[0].strip() == '@'+decoratorName]
0
Skovborg Jensen

この問題を解決する簡単な方法は、渡された各関数/メソッドをデータセット(リストなど)に追加するコードをデコレータに配置することです。

例えば.

def deco(foo):
    functions.append(foo)
    return foo

decoデコレータを持つすべての関数がfunctionsに追加されます。

0
Thomas King