在Python的类型注解体系中,泛型类型允许我们为容器类或函数参数指定更具体的类型约束,比如Tuple[str, int]就表示包含字符串和整数的元组类型。实际开发中,我们可能需要从类型注解字符串或者对应的类型对象中提取出泛型内部的子类型参数,用于类型校验、代码静态分析等场景。

利用typing模块解析类型注解对象
如果已经拿到了类型注解对应的对象,不需要处理字符串,我们可以直接使用Python内置的typing模块提供的能力来提取泛型参数。typing模块中大部分泛型类型都有__args__属性,这个属性会存储泛型内部的子类型参数。
下面是一个提取Tuple子类型的示例:
from typing import Tuple, get_args
# 定义带泛型的类型注解
type_annotation = Tuple[str, int, float]
# 使用get_args获取泛型参数,也可以直接访问__args__属性
sub_types = get_args(type_annotation)
print(sub_types) # 输出 (<class 'str'>, <class 'int'>, <class 'float'>)
# 遍历子类型
for sub_type in sub_types:
print(sub_type)
除了Tuple之外,List、Dict等泛型类型同样支持这种方式,比如List[int]的__args__会返回(<class 'int'>,),Dict[str, int]的__args__会返回(<class 'str'>, <class 'int'>)。
解析类型注解字符串提取参数
如果拿到的是类型注解的字符串形式,比如"Tuple[str, int, float]",我们需要先将其转换为对应的类型对象,再进行参数提取。这里可以使用eval函数配合typing模块的上下文来实现,不过需要注意eval的安全风险,仅在可信输入场景下使用。
示例代码如下:
from typing import Tuple, get_args
import typing
# 类型注解字符串
type_str = "Tuple[str, int, float]"
# 在typing模块的命名空间下执行eval,将字符串转为类型对象
type_obj = eval(type_str, {"__builtins__": None}, vars(typing))
sub_types = get_args(type_obj)
print(sub_types) # 输出 (<class 'str'>, <class 'int'>, <class 'float'>)
如果输入是不可信的字符串,不建议使用eval,可以手动解析字符串内容。手动解析的核心思路是先匹配泛型类型的外层名称,再提取括号内的参数部分,最后分割参数并处理嵌套的泛型情况。
手动解析字符串的实现示例
下面的代码实现了简单的泛型参数提取,支持嵌套泛型的场景:
def parse_generic_params(type_str):
# 去除首尾空格
type_str = type_str.strip()
# 找到第一个左括号的位置
left_idx = type_str.find('(')
if left_idx == -1:
# 不是泛型类型,直接返回空列表
return []
# 提取括号内的内容,处理嵌套括号
param_str = type_str[left_idx+1:-1]
params = []
stack = 0
current = []
for char in param_str:
if char == '[':
stack += 1
current.append(char)
elif char == ']':
stack -= 1
current.append(char)
elif char == ',' and stack == 0:
params.append(''.join(current).strip())
current = []
else:
current.append(char)
if current:
params.append(''.join(current).strip())
return params
# 测试普通Tuple
print(parse_generic_params("Tuple[str, int, float]")) # 输出 ['str', 'int', 'float']
# 测试嵌套泛型
print(parse_generic_params("Tuple[List[str], Dict[str, int]]")) # 输出 ['List[str]', 'Dict[str, int]']
两种方式的适用场景
两种方式各有适用场景,我们可以通过下面的表格对比选择:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 利用typing模块解析对象 | 实现简单,支持所有标准泛型类型,结果准确 | 需要已经拿到类型对象,不能直接处理字符串 | 已经获取类型注解对象的场景,比如函数注解、类注解 |
| 解析类型注解字符串 | 可以直接处理字符串输入,灵活度高 | 手动解析需要处理嵌套等复杂情况,eval方式有安全风险 | 输入为字符串形式,且需要提取参数的场景 |
注意事项
在使用上述方法时需要注意几个问题:
- Python 3.9+ 中内置的tuple、list、dict等类型已经支持泛型注解,比如tuple[str, int],这类类型的
__args__同样可以正常获取参数。 - 手动解析字符串时,如果泛型参数中包含带逗号的复杂类型,比如Callable[[int, str], bool],需要额外处理参数内的逗号,上面的示例仅支持简单的嵌套泛型场景。
- 使用eval解析字符串时,一定要确保输入内容可信,避免执行恶意代码。
通过上述方法,我们可以轻松实现Python类型注解字符串中泛型参数的提取,满足不同开发场景的需求。