如何理解用埃氏筛生成素数的Python代码中yield from的用法?
埃氏筛惰性生成素数代码解析
核心语法先搞懂:yield与yield from
yield是Python生成器的标志,函数只要包含yield就会变成生成器,调用时不会立刻执行代码,每次调用next()才会运行到下一个yield位置,返回对应的值后暂停运行。- 你困惑的
yield from可以简单理解为生成器的代理转发:yield from x等价于自动遍历x的所有元素,逐个yield出来,和下面的代码效果完全一致:
这里的for item in x: yield itemx可以是任意可迭代对象,包括其他生成器。
逐函数拆解
1. nats(n)函数
def nats(n): yield n yield from nats(n+1)
这是一个无限生成器,作用是生成从n开始的所有自然数:
- 调用
nats(2)时,第一次next()返回2,之后转发nats(3)的所有输出 nats(3)第一次next()返回3,再转发nats(4)的输出,以此类推,要多少就生成多少,不会提前计算所有值。
2. sieve(s)核心筛函数
def sieve(s): n = next(s) yield n yield from sieve(i for i in s if i%n != 0)
这就是埃氏筛的惰性实现,逻辑完全对应埃氏筛的核心规则:每找到一个素数,就筛掉序列中所有它的倍数:
- 第一步:从输入的序列
s中取出第一个数n,这个数一定是素数(因为前面所有更小素数的倍数都已经被筛掉了) - 第二步:返回这个素数
n - 第三步:把输入序列剩下的元素中,所有能被
n整除的数过滤掉,用过滤后的新序列再调用sieve,转发新sieve返回的所有素数。
运行逻辑逐次追踪
我们跟着代码里调用next(p)的过程一步步走,就能完全理清:
- 初始化
p = sieve(nats(2)):此时只是创建了生成器对象,没有执行任何内部代码。 - 第一次调用
next(p):
输入序列s是nats(2),next(s)拿到2,返回2后暂停,下一次要从新的sieve(输入是过滤掉所有2倍数的自然数序列:3,5,7,9,11...)拿值。 - 第二次调用
next(p):
新sieve的输入序列取第一个值拿到3,返回3后暂停,下一层输入是过滤掉2、3倍数的序列:5,7,11,13,17... - 第三次调用
next(p):
新一层sieve取第一个值拿到5,返回5,下一层输入过滤掉2、3、5倍数的序列,以此类推。
简化理解的小技巧
如果还是觉得递归加yield from绕,可以把所有yield from替换成等价的for循环写法,代码逻辑完全不变但可读性更高:
# 替换后的nats def nats(n): yield n for num in nats(n+1): yield num # 替换后的sieve def sieve(s): n = next(s) yield n for prime in sieve(i for i in s if i%n != 0): yield prime
内容的提问来源于stack exchange,提问作者Ibrahim Sherif
相关产品推荐
相关产品推荐

