Python generator send()方法异常输出的运行机制咨询
Python生成器send()方法跳值问题解析
问题复现
初始版本的3的倍数生成器运行正常,代码如下:
def MultipleOfThree(): num = 1 while True: if num % 3 == 0: yield num num += 1 #test iter = MultipleOfThree() for m3 in iter: print(m3)
运行后会按顺序输出3、6、9、12、15……所有3的正整数倍。
添加send逻辑修改代码后,输出出现跳数,仅输出3、9、15、21、27,间隔为6,跳过了6、12、18等本该输出的数值,修改后代码如下:
def MultipleOfThree(): num = 1 while True: if num % 3 == 0: i = yield num if i is not None: num = i num += 1 #test iter = MultipleOfThree() for m3 in iter: print(m3) iter.send(m3) #for simplicity send the m3 itself
核心运行规则
出现这个问题的本质是没搞懂生成器的两个基础运行逻辑:
- 所有触发生成器运行的操作(包括
for循环隐式调用next()、手动调用next()、调用send()),都会让生成器从当前暂停的yield位置继续向下执行,直到碰到下一个yield才会暂停,同时把yield关键字后面的值作为本次调用的返回值传出。 send(x)的作用等价于「给当前暂停位置的yield表达式传返回值x」+「执行一次next()让生成器继续跑到下一个yield点」,也就是说 调用一次send,就会消耗掉生成器的一个yield输出。
逐行执行流程拆解
顺着代码跑一遍流程就能看到跳数的原因:
- 初始化生成器
iter = MultipleOfThree()时,生成器停在函数入口,还没执行任何代码。 - for循环第一次隐式调用
next(iter)启动生成器:num从1开始自增,直到num=3时触发i = yield num,把3传出给for循环,生成器暂停在这行,for循环拿到3赋值给m3,打印3。 - 接下来执行
iter.send(3):- 首先把传入的3赋值给当前暂停位置的变量i,因为i不是None,执行
num = i把num设为3; - 继续向下执行
num += 1,num变成4; - 回到while循环继续自增,直到num=6时再次碰到
i = yield num,把6传出——这个6是send()调用的返回值,你没有接收也没有打印,直接被丢弃了,生成器暂停在返回6的yield位置。
- 首先把传入的3赋值给当前暂停位置的变量i,因为i不是None,执行
- for循环进入下一轮,隐式调用
next(iter):- 生成器从返回6的yield位置继续跑,因为是next()调用,没有传入值,所以当前yield表达式的返回值i为None,不触发num赋值逻辑;
- 向下执行
num +=1,num从6变成7,继续自增直到num=9时碰到yield,把9传出给for循环,打印9。
- 之后重复步骤3-4的逻辑:send(9)会让生成器跑到num=12时返回12并丢弃,下一轮for循环的next会跑到num=15打印,最终就出现了间隔6输出的现象。
验证与修正
你可以把send的返回值打印出来,就能看到被跳过的数值:
iter = MultipleOfThree() for m3 in iter: print("for循环打印值:", m3) skip_val = iter.send(m3) print("send调用返回的被跳过值:", skip_val) if m3 > 30: break
运行后就能看到完整的3的倍数序列,只是一半被for循环拿到,一半被send调用拿到。
如果要正常迭代同时使用send,不要用for循环隐式迭代,手动控制生成器运行即可,注意 刚初始化的生成器必须先调用一次next()启动到第一个yield位置,才能传入非None的send值,否则会抛出类型错误:
iter = MultipleOfThree() current = next(iter) print(current) while current < 30: current = iter.send(current) print(current)
内容的提问来源于stack exchange,提问作者Tanzer
相关产品推荐
相关产品推荐

