Python装饰器与函数签名的关系装饰器包装函数后原始函数的签名信息会丢失。inspect.signature正确获取签名的唯一方式是使用functools.wraps。演示签名丢失import inspectdef log_calls(func):def wrapper(*args, **kwargs):print(fCalling {func.__name__})return func(*args, **kwargs)return wrapperlog_callsdef process_data(name: str, count: int 0) - bool:Process the data.return Truesig inspect.signature(process_data)print(sig) # (*args, **kwargs) 而不是 (name: str, count: int 0)wrapper完全隐藏了原始函数签名。inspect.signature返回wrapper的参数列表。使用wraps修复签名from functools import wrapsdef log_calls_proper(func):wraps(func)def wrapper(*args, **kwargs):print(fCalling {func.__name__})return func(*args, **kwargs)return wrapperlog_calls_properdef process_data(name: str, count: int 0) - bool:return Truesig inspect.signature(process_data)print(sig) # (name: str, count: int 0)wraps将__name__、__doc__、__annotations__复制到wrapper。inspect.signature从__annotations__恢复签名。部分修复的局限性log_calls_properdef process_data(name: str, count: int 0) - bool:return Trueprint(process_data.__name__) # process_dataprint(process_data.__doc__) # Process the data.print(process_data.__annotations__) # {name: str, count: int, return: bool}wraps不修复wrapper的默认参数行为。如果用户对wrapper调用inspect.signature返回的签名有正确类型但无法动态修改。手动修复__signature__import inspectfrom functools import wrapsdef preserve_signature(func):sig inspect.signature(func)wraps(func)def wrapper(*args, **kwargs):return func(*args, **kwargs)wrapper.__signature__ sigreturn wrapperpreserve_signaturedef compute(a: int, b: int 0) - int:return a bsig inspect.signature(compute)print(sig) # (a: int, b: int 0) - int__signature__显式设置签名。inspect.signature优先使用__signature__属性。装饰器接收参数时的签名保持def with_logging(levelINFO):def decorator(func):wraps(func)def wrapper(*args, **kwargs):return func(*args, **kwargs)wrapper.__signature__ inspect.signature(func)return wrapperreturn decoratorwith_logging(levelDEBUG)def connect(host: str, port: int 80) - None:passsig inspect.signature(connect)print(sig) # (host: str, port: int 80) - None多层闭包和装饰器时签名保持def compose_decorators(*decorators):def decorator(func):result funcfor dec in reversed(decorators):result dec(result)return resultreturn decoratorcompose_decorators(with_logging(INFO),preserve_signature)def query(sql: str, params: tuple ()) - list:return []sig inspect.signature(query)# 应保持 (sql: str, params: tuple ()) - list对于链式装饰器只有正确实现了wraps或__signature__的装饰器才能保持签名。带签名的装饰器类实现class Timed:def __init__(self, func):wraps(func)(self)self.func funcdef __call__(self, *args, **kwargs):import timestart time.perf_counter()result self.func(*args, **kwargs)elapsed time.perf_counter() - startprint(f{self.func.__name__} took {elapsed:.4f}s)return resultdef __get__(self, obj, objtypeNone):if obj is None:return selfreturn functools.partial(self.__call__, obj)Timeddef heavy_compute(n: int) - int:return sum(range(n))sig inspect.signature(heavy_compute)print(sig) # (n: int) - intwraps(self)将func的元信息复制到Timed实例。__call__接受任何参数。使用装饰器工厂根据条件修改签名import inspectfrom functools import wrapsdef inject_param(name, defaultNone, kindinspect.Parameter.KEYWORD_ONLY):def decorator(func):sig inspect.signature(func)new_param inspect.Parameter(name, kind, defaultdefault)params list(sig.parameters.values())if kind inspect.Parameter.KEYWORD_ONLY:params.append(new_param)else:params.insert(0, new_param)new_sig sig.replace(parametersparams)wraps(func)def wrapper(*args, **kwargs):kwargs[name] kwargs.get(name, default)return func(*args, **kwargs)wrapper.__signature__ new_sigreturn wrapperreturn decoratorinject_param(debug, False)def handle_request(path: str):passsig inspect.signature(handle_request)# (path: str, debug: bool False)inspect.Parameter创建新的参数对象。参数种类有POSITIONAL_ONLY、POSITIONAL_OR_KEYWORD、VAR_POSITIONAL、KEYWORD_ONLY、VAR_KEYWORD。getfullargspec的局限性已弃用import inspectdef decorated():passprint(inspect.getfullargspec(decorated)) # 可能返回不正确的信息getfullargspec只检查__code__和__defaults__不支持__signature__。应该使用inspect.signature。Signature实例的bind方法将参数绑定到签名上sig inspect.signature(handle_request)bound sig.bind(/home, debugTrue)print(bound.arguments) # {path: /home, debug: True}try:sig.bind(/home, extraTrue)except TypeError as e:print(e) # got unexpected keyword argument extrabind验证参数是否合法。bind_partial允许部分绑定。装饰器内部通过signature实现参数校验import inspectdef validate(func):sig inspect.signature(func)wraps(func)def wrapper(*args, **kwargs):bound sig.bind(*args, **kwargs)bound.apply_defaults()for name, value in bound.arguments.items():param sig.parameters[name]if param.annotation ! inspect.Parameter.empty:expected param.annotationif not isinstance(value, expected):raise TypeError(fArgument {name} must be {expected.__name__}, fgot {type(value).__name__})return func(*args, **kwargs)wrapper.__signature__ sigreturn wrapper