中学理科1625756 views
高校化学2912417 views
雑学1472443 views
高校国語785262 views
いろは2981760 views
中学英語808125 views
LaTeX956652 views
英語606840 views
りんご189887 views
数学講師2847003 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 の補完やドキュメント生成も正しく動作します。