如何让生成器支持 .send() 但保持类型提示正确

12次阅读

Generator类型默认不支持.send()类型检查的根本原因是未完整声明三元泛型参数;正确写法是Generator[YieldType, SendType, ReturnType],缺一不可,否则SendType默认为Any,导致类型校验失效。

如何让生成器支持 .send() 但保持类型提示正确

为什么 Generator 类型默认不支持 .send() 的类型检查?

python 标准库typing.Generator[YieldType, SendType, ReturnType] 确实支持 .send(),但很多人只写 Generator[YieldType](等价于 Generator[YieldType, Any, Any]),这时 .send() 接收参数的类型被推为 Anyide 或 mypy 不会校验传入值是否合法,也容易掩盖 bug

根本原因不是“不支持”,而是类型参数没写全 —— 少了 SendTypeReturnType工具就无法推断 .send() 的输入/输出契约。

正确声明三元泛型:显式写出 SendTypeReturnType

只要生成器体内部用了 yield 接收值(即 x = yield y),就必须在类型中明确 SendType;如果生成器可能通过 return expr 终止,且需要捕获返回值,则 ReturnType 也不能是 None

  • Generator[int, str, bool] 表示:每次 yield 出一个 int,接受 str 类型的 .send() 输入,最终 return 一个 bool
  • 若从不 return 值(或只 return 无值),ReturnType 可设为 None;mypy 默认要求它,不能省略
  • 若生成器从不接收 .send() 值(即没有 x = yield 形式),SendType 应设为 None,此时调用 .send(val) 会报错(符合预期)

示例:

from typing import Generator 

def echo_int() -> Generator[int, str, None]: while True: s = yield 42 # 接收 str,产出 int if s == "stop": return

常见错误:用 IteratorIterable 替代 Generator

Iterator[T] 只定义了 __next__(),没有 .send() 方法;Iterable[T] 更只保证能 for 循环。两者在类型系统里都不含发送能力,强行调用 .send() 会被 mypy 直接拒绝。

  • 不要写 def f() -> Iterator[int]: ... 然后在里面用 yield x 接收值 —— 类型和实现矛盾
  • 也不要用 Iterable 做返回类型来“绕过”检查,这会让类型提示完全失效
  • 即使函数逻辑上是单次生成(如 yield once),只要用了 yield 表达式(带右赋值),就必须用完整 Generator

pycharm / mypy 实际校验要点

工具.send() 的检查非常严格:不仅校验参数类型,还检查是否可能向已终止或未启动生成器发送值(运行时异常,但类型系统不捕获)。重点注意:

  • mypy 要求 .send() 第一次调用必须传 None(除非生成器已用 next()__next__() 启动),但类型层面无法强制这个约束 —— 需靠文档或封装函数兜底
  • PyCharm 在你写 gen.send("hello") 时,会比对 SendType;如果类型不符,立刻标红并提示 “Expected type ‘str’, got ‘int’ instead”
  • 若生成器有多个 yield,且接收不同类型(比如 yield 后有时接 int、有时接 str),类型系统无法表达这种分支,只能取联合类型如 union[int, str],这时应考虑拆分逻辑或改用类协程

最易被忽略的一点:生成器函数本身返回的是 Generator 实例,但它的类型签名必须写全三个参数,否则所有发送行为在类型层面就“不可见”了 —— 不是语法限制,而是契约缺失。

text=ZqhQzanResources