如何从字符串还原带泛型的Python函数参数类型与默认值?
问题描述
我通过inspect模块获取函数参数的注解与默认值,将其序列化为字符串形式的字典,代码如下:
import inspect, importlib from pydoc import locate def func(var:str = 'test', var2: int = 1, var3:list[int] = [1,2,3]): pass fspec = inspect.getfullargspec(func) dflts = fspec.defaults spec_dict = {} for i, (a) in enumerate(fspec.annotations.items()): type_name = a[1].__name__ if type(a[1]) is type else str(a[1]) spec_dict[a[0]] =(str(dflts[i]), str(type_name)) print(spec_dict)
生成的字典:
{'var': ('test', 'str'), 'var2': ('1', 'int'), 'var3': ('[1, 2, 3]', 'list[int]')}
之后尝试将字符串值转回对应类型,编写了如下代码:
for v in spec_dict.values(): val_as_str = v[0] param_type = locate(v[1]) print(param_type(val_as_str))
但处理list[int]时,pydoc.locate返回None导致失败。由于需要通过JSON序列化反序列化实现函数签名的还原,求正确的处理方式。
解决方案
1. 正确获取类型注解:用typing.get_type_hints替代inspect.getfullargspec
inspect.getfullargspec获取的泛型注解(如list[int])只是字符串形式,而typing.get_type_hints能拿到真实的类型对象,方便后续序列化时保存准确的类型结构信息。
修改获取注解与默认值的代码:
import inspect from typing import get_type_hints def func(var:str = 'test', var2: int = 1, var3:list[int] = [1,2,3]): pass type_hints = get_type_hints(func) fspec = inspect.getfullargspec(func) dflts = fspec.defaults spec_dict = {} # 匹配参数与对应默认值(默认值顺序为从最后一个参数开始) args_with_defaults = fspec.args[-len(dflts):] if dflts else [] for arg, default in zip(args_with_defaults, dflts): param_type = type_hints[arg] # 序列化类型:区分普通类型与泛型类型 if hasattr(param_type, '__origin__'): type_info = { 'origin': param_type.__origin__.__name__, 'args': [t.__name__ if hasattr(t, '__name__') else str(t) for t in param_type.__args__] } else: type_info = {'type': param_type.__name__} # 用repr()序列化默认值,保证能被安全解析 spec_dict[arg] = (repr(default), type_info) print(spec_dict)
生成的字典会包含泛型的结构信息,比如var3的类型信息为{'origin': 'list', 'args': ['int']},而非字符串list[int]。
2. 反序列化时还原类型与默认值
- 用
ast.literal_eval解析默认值字符串,比eval更安全,能正确还原列表、字典等复杂结构。 - 对于泛型类型,通过
typing模块的内置对象构造;对于普通类型,通过模块导入获取。 - 支持递归处理嵌套泛型(如
list[dict[str, int]])。
反序列化代码示例:
import ast from typing import * def restore_type(type_info): if 'type' in type_info: # 处理普通类型,支持自定义模块类型 if '.' in type_info['type']: module_name, type_name = type_info['type'].split('.', 1) else: module_name, type_name = 'builtins', type_info['type'] module = __import__(module_name) return getattr(module, type_name) elif 'origin' in type_info: # 处理泛型类型 origin = typing.__dict__.get(type_info['origin']) args = [restore_type({'type': arg}) for arg in type_info['args']] return origin[tuple(args)] def restore_default(val_str): return ast.literal_eval(val_str) # 反序列化示例 for arg, (val_str, type_info) in spec_dict.items(): param_type = restore_type(type_info) default_val = restore_default(val_str) print(f"{arg}: {default_val} (类型: {param_type}, 实际类型: {type(default_val)})")
3. 适配JSON序列化
JSON仅支持基本类型,上述代码生成的type_info为字典结构,可直接序列化。序列化与反序列化示例:
import json # 序列化到JSON文件 json_str = json.dumps(spec_dict) with open('func_spec.json', 'w') as f: f.write(json_str) # 从JSON文件反序列化 with open('func_spec.json', 'r') as f: loaded_spec = json.load(f) # 还原类型与默认值 for arg, (val_str, type_info) in loaded_spec.items(): param_type = restore_type(type_info) default_val = restore_default(val_str) print(f"{arg}: {default_val} (类型: {param_type})")
注意事项
- 必须用
repr()而非str()处理默认值,确保字符串、列表等结构的表示能被ast.literal_eval正确解析。 - 自定义类型需保证所在模块能被导入,可扩展
restore_type函数处理自定义模块路径。 - 对于
Union[int, str]这类特殊泛型,需在restore_type中增加对应判断逻辑,构造正确的联合类型。
内容的提问来源于stack exchange,提问作者mike01010
相关产品推荐
相关产品推荐

