@ decorator2で装飾された特定のクラスAのすべてのメソッドを取得する方法は?
class A():
def method_a(self):
pass
@decorator1
def method_b(self, b):
pass
@decorator2
def method_c(self, t=5):
pass
私はすでにこの質問に答えました: Pythonの配列インデックスによる関数の呼び出し =)
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 ...>
_を返すこともできます。しかし、この方法はハック的でひどいものです。
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
_を調べて目的のセマンティクスを実現することで、それらを無視するロジックを簡単に作成できます。
方法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())"]}
たぶん、デコレータがあまり複雑ではない場合(しかし、あまりハッキングの方法があるかどうかはわかりません)。
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
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]
この問題を解決する簡単な方法は、渡された各関数/メソッドをデータセット(リストなど)に追加するコードをデコレータに配置することです。
例えば.
def deco(foo):
functions.append(foo)
return foo
decoデコレータを持つすべての関数がfunctionsに追加されます。