非常に一般的なことを行うデコレータを書いたとしましょう。たとえば、すべての引数を特定の型に変換したり、ロギングを実行したり、メモ化を実装したりできます。
次に例を示します。
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
>>> funny_function("3", 4.0, z="5")
22
これまでのところすべてが順調です。ただし、問題が1つあります。装飾された関数は、元の関数のドキュメントを保持しません。
>>> help(funny_function)
Help on function g in module __main__:
g(*args, **kwargs)
幸い、回避策があります。
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
今回は、関数名とドキュメントが正しいです:
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(*args, **kwargs)
Computes x*y + 2*z
しかし、まだ問題があります。関数のシグネチャが間違っています。「* args、** kwargs」という情報は役に立たないものの隣にあります。
何をすべきか?2つの単純だが欠陥のある回避策を考えることができます。
1-docstringに正しい署名を含めます。
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
これは重複のために悪いです。自動生成されたドキュメントでは、署名は適切に表示されません。関数を更新してdocstringの変更を忘れたり、タイプミスをするのは簡単です。[ はい、私はdocstringがすでに関数本体を複製していることを知っています。これを無視してください。funny_functionは単なるランダムな例です。]
2-デコレーターを使用しないか、特定の署名ごとに専用のデコレーターを使用します。
def funny_functions_decorator(f):
def g(x, y, z=3):
return f(int(x), int(y), z=int(z))
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
これは、同一のシグネチャを持つ一連の関数では問題なく機能しますが、一般的には役に立ちません。最初に述べたように、デコレータを完全に汎用的に使用できるようにしたいと考えています。
私は完全に一般的で自動化されたソリューションを探しています。
だから問題は、装飾された関数のシグネチャが作成された後で編集する方法はあるのか?
それ以外の場合、装飾された関数を作成するときに、関数のシグネチャを抽出し、「* kwargs、** kwargs」の代わりにその情報を使用するデコレータを作成できますか?どうすればその情報を抽出できますか?装飾された関数を構築するにはどうすればよいですか-execを使用して?
他のアプローチはありますか?
inspect.Signature
装飾された関数を処理するために何が追加されたのか、多かれ少なかれ疑問に思いました。