如何在Python 3.13中利用PEP 646和PEP 695创建支持异构类型的可变泛型容器?
如何在Python 3.13中利用PEP 646和PEP 695创建支持异构类型的可变泛型容器?
我来帮你解决这个问题!你的思路完全正确,但在可变泛型的类型标注细节上需要调整,同时要注意类型检查器的特性支持要求。下面是修正后的完整代码和详细解释:
修正后的代码
from typing import Callable, Unpack class Operation[R]: """Encapsulates a function returning type R.""" def __init__(self, fn: Callable[[], R]) -> None: self._fn = fn def run(self) -> R: return self._fn() class OperationSequence[*Ts]: """ A typed-tuple container for Operation objects, each possibly returning a different type in Ts. For example, OperationSequence[int, str] means item[0] returns int, item[1] returns str, etc. """ def __init__(self, *ops: Unpack[tuple[Operation[*Ts]]]) -> None: self._ops = ops # 类型被推断为 tuple[Operation[*Ts]] def __getitem__(self, index: int) -> Operation[Ts[index]]: return self._ops[index] def __len__(self) -> int: return len(self._ops) def run_all(self) -> tuple[*Ts]: # 正确返回与Ts对应的异构元组类型 return tuple(op.run() for op in self._ops) # 示例用法 def get_number() -> int: return 42 def get_text() -> str: return "Hello" ops = OperationSequence( Operation(get_number), # 对应 Ts[0] = int Operation(get_text), # 对应 Ts[1] = str ) # 现在类型检查器会正确推断: # ops.run_all() -> tuple[int, str] # ops[0].run() -> int # ops[1].run() -> str print(ops.run_all()) # 输出: (42, "Hello")
关键问题修正与解释
1. 构造函数的类型标注错误
你之前写的*ops: Operation[*Ts]是错误的,因为Operation只接受单个类型参数,而*Ts是一个类型变量元组。
修正方案:使用Unpack展开由Operation[Ts[0]], Operation[Ts[1]], ...组成的元组类型,这样每个传入的Operation对象都会对应Ts中的一个具体类型,类型检查器就能正确校验传入的参数类型。
2. __getitem__的类型标注
你的思路完全正确,Ts[index]可以正确索引可变泛型中的对应类型,但需要注意:
- 确保使用支持PEP 646的类型检查器版本(见下方注意事项)
- 当使用字面量索引(如
ops[0])时,类型检查器会精确推断出对应的Operation类型;如果使用变量索引(如i=0; ops[i]),类型检查器会返回所有可能类型的联合(如Union[Operation[int], Operation[str]]),这是合理的,因为运行时索引可能是任意整数。
注意事项
- 类型检查器版本要求:
- mypy: 1.5+,运行时需添加选项
--enable-incomplete-features=variadic-generics - pyright: 1.1.320+,默认支持PEP 646和PEP 695特性
- mypy: 1.5+,运行时需添加选项
run_all方法的返回类型tuple[*Ts]是正确的,类型检查器会自动推断为与Ts对应的异构元组类型
备注:内容来源于stack exchange,提问作者Arthur Brenno




