Python 3.8中yield from触发StopIteration报错的原因咨询
问题描述
我是生成器的新手,我的理解是:当从生成器函数total_average中break时,会在wrap_average中隐式触发StopIteration,但wrap_average会向调用者返回None,程序不应出现StopIteration。但在Python 3.8中执行以下测试代码时,w.send(None)抛出了StopIteration错误,不清楚原因是什么?
测试代码:
def total_average(): total = 0.0 count = 0 avg = None print("starting average generator") while True: num = yield avg if num is None: break total += num count += 1 avg = total/count def wrap_average(average_generator): """This is just a pipe to the generator""" print("starting wrap_average") avg = yield from average_generator # Note: total_average() is the generator object. total_average is generator function w = wrap_average(total_average()) # Execute everthing until hitting the first iterator. None being returned print("starting the generator: ", next(w)) print(w.send(3)) print(w.send(4)) # Finish Coroutine # ? Not sure why w.send(None) is giving me stop iteration? w.send(None)
原因解析
当total_average中执行break时,生成器会直接终止,此时会抛出StopIteration异常。而wrap_average里的yield from语句会将子生成器(total_average)的终止信号直接向上传递——子生成器抛出的StopIteration不会被wrap_average捕获并转为返回值,而是会透传到外层的调用者,导致你看到的错误。
你可能误解了yield from的行为:avg = yield from average_generator确实会将子生成器终止时StopIteration的value属性赋值给avg(这里因为子生成器没有return,所以value是None),但这个赋值操作并不会阻止StopIteration异常向外层传播,外层调用者仍然会收到这个异常。
解决方法
如果不想让外层抛出StopIteration,可以通过两种方式处理:
方式一:在wrap_average中捕获StopIteration
修改wrap_average函数,显式捕获子生成器的终止异常:
def wrap_average(average_generator): """This is just a pipe to the generator""" print("starting wrap_average") try: avg = yield from average_generator except StopIteration: return avg
方式二:让total_average用return终止(更符合生成器规范)
将total_average中的break替换为return,返回当前计算的平均值,这样yield from会将这个返回值作为自己的返回值,同时不会向外传播StopIteration:
def total_average(): total = 0.0 count = 0 avg = None print("starting average generator") while True: num = yield avg if num is None: return avg # 用return替代break,返回最终平均值 total += num count += 1 avg = total/count
修改后再执行代码,w.send(None)就不会抛出StopIteration异常了。
内容的提问来源于stack exchange,提问作者Ruotong Jia

