Python中如何为异构生成器添加类型注解?
在无类型标注的Python中,我使用生成器而非直接返回元组,示例代码如下(Python 3.11):
def generate_plant(): """ Generates fields for a sunflower. """ # label yield 'A1' # height in inches yield 72 # hours of sunlight daily yield 8.5 def get_plant(): """ Returns fields as a tuple. """ return ('A1', 72, 8.5) # save the results from_generator = tuple(generate_plant()) direct_tuple = get_plant() # compare print('From generator:', from_generator) print('Directly from tuple:', direct_tuple) print('Are they equal?', from_generator==direct_tuple)
上述代码中,from_generator与direct_tuple的值完全相等。这种方式让调用方可以选择所需的集合类型,或仅获取前几个字段。
但在类型标注时,collections.abc中的生成器属于同构可迭代对象(元组是特例),目前我能做到的最接近的标注是def generate_plant() -> collections.abc.Iterator[str | int | float],但该标注过于宽泛,无法像get_plant的tuple[str, int, float]那样体现返回类型的顺序。
请问:
- 当前是否只能在使用异构生成器和精准类型标注中二选一?
- 是否有办法指定生成器按顺序返回字符串、整数、浮点数?
- 使用异构生成器是否属于误用?
目前我只能选择上述宽泛的类型标注,或直接使用元组的方式,期望找到能指定返回类型顺序的方案。
是否只能在异构生成器和精准标注中二选一?
目前(Python 3.11及主流类型检查器生态),原生的Iterator/Generator类型确实无法直接标注有序异构的返回序列,因为生成器本质上是流式可迭代对象,类型系统默认将其视为同构迭代器。但可以通过变通方案接近精准标注,并非完全只能二选一。有没有办法指定生成器按顺序返回字符串、整数、浮点数?
有两种可行的变通方式:- 自定义迭代协议(依赖类型检查器支持)
通过typing.Protocol定义一个重载__next__方法的协议,模拟生成器按顺序返回指定类型。这种方式在Pyright中支持较好,mypy需要额外配置:from typing import Protocol from collections.abc import Iterator class PlantGeneratorProtocol(Protocol): def __next__(self) -> str: ... def __next__(self) -> int: ... def __next__(self) -> float: ... def __iter__(self) -> "PlantGeneratorProtocol": ... def generate_plant() -> PlantGeneratorProtocol: yield 'A1' yield 72 yield 8.5 - 结合重载标注与注释
既然调用方常将生成器转为元组,可以用@overload标注转换后的元组类型,同时保留迭代器的宽泛标注,兼顾类型检查和可读性:
这种方式能让类型检查器识别from typing import overload from collections.abc import Iterator @overload def generate_plant() -> tuple[str, int, float]: ... @overload def generate_plant() -> Iterator[str | int | float]: ... def generate_plant(): yield 'A1' yield 72 yield 8.5tuple(generate_plant())的类型为tuple[str, int, float],直接迭代生成器时则仍为宽泛类型。另外,也可以在函数文档字符串中明确标注返回顺序,辅助开发者理解。
- 自定义迭代协议(依赖类型检查器支持)
使用异构生成器是否属于误用?
不算误用。只要你的场景需要生成器的灵活性——比如调用方可能只需要前几个字段、生成过程需要逐步计算无法一次性构建元组、或处理大量数据时节省内存——异构生成器就是合理的选择。类型标注的局限性不应否定其在实际场景中的价值。
内容的提问来源于stack exchange,提问作者L Duran

