Python
python3

ラップする関数のシグネチャを維持したラッパーを作成する方法

特に関数デコレータでは、引数として受け取った修飾する関数をラップする関数を作成して返すことがよくありますが、ラップする関数のシグネチャをラップされる関数と同じにしたい時があります。
Python 3.4以降では、標準ライブラリfunctoolsが提供するwrapsというデコレータを使ってこの実装が可能です。

以下の例では、デコレータargs_as_intsで修飾後のfunny_function(つまりfunctools.wrapsで修飾後のwrapper)は、修飾される関数funny_functionと同じシグネチャを持ちますが、全ての引数をintに変換して元の関数を同じ計算を行います。

# Source: https://stackoverflow.com/questions/147816/preserving-signatures-of-decorated-functions

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

修飾された後のfunny_functionには、__wrapped__というメンバーがあり、元々の関数が保持されています。

関数のシグネチャを調べるには、標準ライブラリinspectを用います。inspectで提供されているsignature関数を用いると、関数のシグネチャを、Signatureオブジェクトとして取り出すことができます。次のコードでは、funny_functionのシグネチャが、修飾前後で同じであることを確かめています。

>>> from inspect import signature
>>> str(signature(funny_function))
'(x, y, z=3)'
>>> signature(funny_function) == signature(funny_function.__wrapped__)
True