优雅处理生成器外部throw的关键是分层拦截与类型前置校验:先用isinstance或duck-typing校验send值类型并主动raise明确异常,再用精准try-catch围住yield及后续轻量逻辑,按valueerror→typeerror→exception顺序捕获,配合else产出正常结果、finally清理资源,并统一自定义异常契约。

单个生成器内部用一个 try-catch 块应对多个外部 throw,关键不在“堆异常”,而在“分层拦截 + 类型前置校验”。生成器本身不主动抛错,但接收外部 send() 传入的值后,若未做类型检查就直接运算,容易在 yield 后续逻辑中触发多种异常(如 TypeError、ValueError、KeyError)。真正优雅的做法是:把类型校验放在 yield 接收值之后、业务逻辑之前,用明确判断替代被动捕获;再配合 try-catch 守住边界,而非等错误冒泡。
先做类型校验,再进业务逻辑
生成器收到 send() 值后,不要直接参与计算或字典访问。先用 isinstance() 或 duck-typing 判断是否符合预期类型,不符合就 raise 明确异常(如 TypeError),而不是让后续操作自然崩出 KeyError 或 AttributeError。
- 比如接收一个参数用于字典查找,先检查是否为 str,再 check key 是否存在
- 接收数字参与除法,先确认是否为数值类型且非零,避免 ZeroDivisionError 在深层逻辑中才暴露
- 对可迭代对象做 len() 或索引前,先 hasattr(obj, '__iter__') 或 isinstance(obj, (list, tuple, str))
try-catch 放在 yield 行为边界上
不是包裹整个生成器函数体,而是精准围住 yield 表达式及紧随其后的轻量级处理逻辑。这样既能捕获 send() 触发的即时异常(如 ValueError),又不会干扰生成器状态机的正常流转。
- catch 要按具体类型从细到粗排列:ValueError → TypeError → Exception
- 避免在 except 块里调用可能再抛异常的操作(比如写日志文件失败)
- 可在 except 中用 yield 返回统一错误信号(如 {'status': 'error', 'reason': str(e)}),保持接口一致性
用 else 分离“安全路径”,用 finally 清理副作用
当 yield 成功接收值、类型校验通过、核心逻辑执行完毕,就进入 else 分支——这里放真正有效的数据产出或状态更新。finally 不用于业务,只做确定性清理,比如关闭临时句柄、重置局部标记位。
- else 块确保只有无异常时才 yield 正常结果,避免部分计算污染输出
- finally 适合释放生成器内部申请的资源(如临时缓存、连接计数器),不依赖外部调用者
- 不建议在 finally 中修改 yield 流或抛新异常,会打乱控制流
对外暴露清晰的异常契约
生成器本身不隐藏错误,但要统一向上反馈方式。比如所有校验失败都 raise ValidationError(自定义异常),所有系统级问题 raise GeneratorRuntimeError。调用方只需捕获这两类,无需猜是哪一行崩的。
- 自定义异常继承 Exception,带 code 和 field 属性,便于前端或日志分类
- send() 调用侧应始终包裹 try-except,并区分处理业务异常与运行时异常
- 文档注明该生成器可能 raise 的异常类型及触发条件,降低集成成本











