如何为Python函数动态分配函数签名?
动态为函数分配签名的装饰器实现方案
我希望创建一个装饰器,能为被装饰函数动态分配函数签名,示例代码如下:
import inspect signature = inspect.Signature([ inspect.Parameter('x', kind=inspect.Parameter.KEYWORD_ONLY, default=None), inspect.Parameter('y', kind=inspect.Parameter.KEYWORD_ONLY, default=None), inspect.Parameter('z', kind=inspect.Parameter.KEYWORD_ONLY, default=None)]) def with_signature(signature: inspect.Signature): def _decor(f): def _wrapper(): ... return _wrapper return _decor @with_signature(signature) def fun(): return [x, y, z]
此时fun应具备与def fun(x=None, y=None, z=None)定义一致的签名,同时希望支持签名中的类型提示。该如何实现?
我的尝试1:粗糙的实现方案
我想出了一个略显粗糙的方案:
import functools import types def with_signature(signature: inspect.Signature): def _decor(f): @functools.wraps(f) def _wrapper(**kwargs): # 校验参数是否符合签名 signature.bind(**kwargs) # 以kwargs作为全局变量创建新函数并执行 _f = types.FunctionType( f.__code__, kwargs ) return _f() return _wrapper return _decor
使用方式如下:
signature = inspect.Signature([ inspect.Parameter('x', kind=inspect.Parameter.KEYWORD_ONLY, default=None), inspect.Parameter('y', kind=inspect.Parameter.KEYWORD_ONLY, default=None), inspect.Parameter('z', kind=inspect.Parameter.KEYWORD_ONLY, default=None)]) @with_signature(signature) def fun(): return [x, y, z] print(fun(x=1, z=3, y=2)) # 输出 [1,2,3]
该方案通过signature.bind校验**kwargs,再以**kwargs作为全局变量创建函数,但无法真正修改被装饰函数的签名,亟待优化。
我的尝试2:更符合Python风格的替代方案
我发现这种实现方式不太符合Python风格,实际需求是提供明确的函数接口,因此使用**kwargs结合typing_extension.Unpack与typing.TypedDict或许是更佳方案:
from typing import TypedDict from typing_extensions import Unpack class Signature(TypedDict): x: int = 0 y: int = 0 z: int = 0 def fun(**kwargs: Unpack[Signature]): x = kwargs.get('x', 0) y = kwargs.get('y', 0) z = kwargs.get('z', 0) return [x, y, z]
目前我会采用该方案,但仍希望得到原问题的解决方案建议。
解决方案建议
要实现真正修改函数签名的装饰器,核心是为包装后的函数设置__signature__属性,同时处理参数绑定和类型提示,具体实现如下:
import inspect import functools def with_signature(signature: inspect.Signature): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): # 绑定参数到签名,自动处理默认值和参数校验 bound_args = signature.bind(*args, **kwargs) bound_args.apply_defaults() # 将绑定后的参数作为关键字参数传入原函数 return func(**bound_args.arguments) # 为包装函数设置签名,让inspect和类型检查工具识别 wrapper.__signature__ = signature # 同步类型提示(如果原函数有) if hasattr(func, '__annotations__'): # 合并签名中的参数类型提示到包装函数 for param in signature.parameters.values(): if param.annotation is not inspect.Parameter.empty: wrapper.__annotations__[param.name] = param.annotation # 保留原函数的返回值类型 if 'return' in func.__annotations__: wrapper.__annotations__['return'] = func.__annotations__['return'] return wrapper return decorator
使用示例
# 定义带类型提示的签名 signature = inspect.Signature([ inspect.Parameter('x', kind=inspect.Parameter.KEYWORD_ONLY, default=None, annotation=int), inspect.Parameter('y', kind=inspect.Parameter.KEYWORD_ONLY, default=None, annotation=int), inspect.Parameter('z', kind=inspect.Parameter.KEYWORD_ONLY, default=None, annotation=int) ], return_annotation=list[int]) @with_signature(signature) def fun(**kwargs): return [kwargs['x'], kwargs['y'], kwargs['z']] # 验证签名 print(inspect.signature(fun)) # 输出 (x: int = None, y: int = None, z: int = None) -> list[int] # 调用测试 print(fun(x=1, y=2, z=3)) # 输出 [1,2,3]
方案说明
- 真实修改签名:通过设置
wrapper.__signature__ = signature,让inspect模块、IDE类型检查工具都能识别到新的函数签名。 - 参数校验与绑定:使用
signature.bind和apply_defaults自动处理参数校验、默认值填充,避免手动处理**kwargs的繁琐。 - 类型提示支持:同步签名中的参数类型和原函数的返回值类型到包装函数,确保类型检查工具正常工作。
- 兼容原函数逻辑:原函数可以通过
**kwargs接收所有参数,或者根据需要直接使用参数名(如果装饰器绑定后传入的是关键字参数)。
如果希望原函数不需要写**kwargs,可以结合exec动态生成适配层,但这种方式可读性较差,不如上述方案稳妥。
内容的提问来源于stack exchange,提问作者lupl
相关产品推荐
相关产品推荐

