特に関数デコレータでは、引数として受け取った修飾する関数をラップする関数を作成して返すことがよくありますが、ラップする関数のシグネチャをラップされる関数と同じにしたい時があります。
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