1. ホーム
  2. python

[解決済み] 装飾された関数の署名の保存

2022-09-08 11:07:48

質問

非常に汎用的なことを行うデコレータを書いたとします。たとえば、すべての引数を特定の型に変換する、ロギングを実行する、メモ化を実装する、などです。

以下はその例です。

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" の代わりにその情報を使用するデコレータを書くことができますか?どうすればその情報を抽出できるのでしょうか? どのようにデコレートされた関数を構築すればよいのでしょうか?

他のアプローチは?

どのように解決するのですか?

  1. インストール デコレーター モジュールをインストールします。

    $ pip install decorator
    
    
  2. の定義を適応する。 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() stdlibから は、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 .