非常に一般的なことを行うデコレータを作成したとします。たとえば、すべての引数を特定の型に変換したり、ロギングを実行したり、メモ化を実装したりできます。
次に例を示します。
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 を使用して?
他のアプローチはありますか?