Computer365519 views
いろは2989323 views
高校化学2914333 views
ヒストリア284772 views
教育148971 views
中学数学621615 views
中学社会667260 views
高校倫理1434006 views
高校日本史189930 views
英語608578 views
Help
Tools

English

Python でシグネチャを保持するデコレータ(inspect.signature の活用)

functools.wraps を使うだけでは、デコレートされた関数のシグネチャが失われます。inspect.signature を活用すると、元のシグネチャを完全に保持できます。

問題の確認

from functools import wraps
import inspect

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator
def greet(name: str, greeting: str = "Hello") -> str:
    return f"{greeting}, {name}!"

print(inspect.signature(greet))
# (*args, **kwargs) ← 元のシグネチャが失われている

wraps はメタデータをコピーしますが、シグネチャは wrapper のものになります。

signature を設定する

from functools import wraps
import inspect

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    wrapper.__signature__ = inspect.signature(func)
    return wrapper

@decorator
def greet(name: str, greeting: str = "Hello") -> str:
    return f"{greeting}, {name}!"

print(inspect.signature(greet))
# (name: str, greeting: str = 'Hello') -> str

引数を追加するデコレータ

新しい引数を追加する場合、シグネチャも更新する必要があります。

from functools import wraps
import inspect

def with_debug(func):
    sig = inspect.signature(func)
    new_param = inspect.Parameter(
        "debug",
        inspect.Parameter.KEYWORD_ONLY,
        default=False
    )
    new_params = list(sig.parameters.values()) + [new_param]
    new_sig = sig.replace(parameters=new_params)
    
    @wraps(func)
    def wrapper(*args, debug=False, **kwargs):
        if debug:
            print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        return func(*args, **kwargs)
    
    wrapper.__signature__ = new_sig
    return wrapper

@with_debug
def add(a: int, b: int) -> int:
    return a + b

print(inspect.signature(add))
# (a: int, b: int, *, debug=False) -> int

引数の検証にシグネチャを活用

def validated(func):
    sig = inspect.signature(func)
    
    @wraps(func)
    def wrapper(*args, **kwargs):
        bound = sig.bind(*args, **kwargs)
        bound.apply_defaults()
        return func(*bound.args, **bound.kwargs)
    
    wrapper.__signature__ = sig
    return wrapper

sig.bind() は引数をパラメータにマッピングし、不足や過剰を検出します。シグネチャを保持することで、IDE の補完やドキュメント生成も正しく動作します。