非常に一般的なことを行うデコレータを作成したとします。たとえば、すべての引数を特定の型に変換したり、ログ記録を実行したり、メモ化を実装したりします。
次に例を示します。
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 -- ドキュメント文字列に正しい署名を含めます。
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
これは重複しているため、よくありません。自動生成されたドキュメントでは、署名は適切に表示されません。関数を更新してドキュメント文字列の変更を忘れたり、タイプミスをしたりすることはよくあります。[はい、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 を使用?
他のアプローチはありますか?
ベストアンサー1
インストールデコレータモジュール:
$ pip install decorator
定義を適応する
args_as_ints()
:import decorator @decorator.decorator def args_as_ints(f, *args, **kwargs): args = [int(x) for x in args] kwargs = dict((k, int(v)) for k, v in kwargs.items()) return f(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x*y + 2*z print funny_function("3", 4.0, z="5") # 22 help(funny_function) # Help on function funny_function in module __main__: # # funny_function(x, y, z=3) # Computes x*y + 2*z
Python 3.4以上
functools.wraps()
標準ライブラリからPython 3.4 以降の署名を保持します:
import functools
def args_as_ints(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return func(*args, **kwargs)
return wrapper
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
print(funny_function("3", 4.0, z="5"))
# 22
help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(x, y, z=3)
# Computes x*y + 2*z
functools.wraps()
利用可能です少なくともPython 2.5以降しかし、署名は保存されません。
help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(*args, **kwargs)
# Computes x*y + 2*z
注意:*args, **kwargs
の代わりにx, y, z=3
。