在MPGI4的第二次作业中,出现了 xrange() 相关用法,在查找其与 range 有什么不同时,发现了 generatoryield 等一系列概念,特此总结。


yield 的用法起源于对一般 functionreturn 的扩展。在一个 function 中,必须有一个返回值列于 return 之后,可以返回数字也可以返回空值,但是必须要有一个返回值,标志着这个 function 的结束。一旦它结束,那么这个 function 中产生的一切变量将被统统抛弃,有什么可以使一个 function 暂停下来,并且返回当前所在地方的值,当接收到继续的命令时可以继续前进呢?换个说法,就是 return 返回一个值,并且记住这个返回的位置。这个操作有点儿像

for x in range():
    pass

这个循环语句中 range() 这个函数干的事情,它给出一个数,当循环完一次以后再在前一个数的基础上拿出另外一个数。但是这里的函数其实是自动生成一个 list 然后从这个 list 第一个开始往后一个一个给出,那么这种方法的劣势显而易见——会有生成一个 list 。也就是说不管你的 for 循环用几回,它都会生成这个 list (比如你有一个庞大的数据库,那么光是生成一个表单就需要很长时间,然而也许你要用的东西就在前几个,那么你建起这个检索目录远远超过你实际所需要的)。这时就是 xrange() 登场的时候了,它就是一个不需要建立 list 却一样完成任务的好命令。那么 xrange() 是什么呢?

xrange() 其实是一个 generator 生成器,不同于一般的 function,里面就包含着yieldgenerator 人如其名,是一个生成器,生成什么呢?不妨把它看作生成一组你需要的序列的函数,通常情况下我们只需要自然数这种简单的序列,但是如果让你生成一组斐波那契数列呢?这组数列是无穷的,你想沿着这个数列一直算下去,直到算到你满意的一个数,但是如果你也不知道这个数在哪里,那么你就不能提前给出一个这个数列,因为你不知道在哪里会停下来。面对这个问题时,最好的解决方法就是记住当前的数记住当前的地址,查看一个这个数符合不符合要求,如果不符合要求,那么继续从这个数/地址开始计算下一个斐波那契数字。这就是 yield 要干的事情了。(其实这么做的好处还有一个就是减少斐波那契数列的运算量,这个数列因为是自循环数列,所以如果按照循环算法的话需要很大的运算量,但是如果是采用动态纪录法的话,那么就会非常简单。这属于优化算法范畴,暂且按下不表。)下面就是斐波那契数列用 yield 的实现法:

# 斐波那契数列:每一个数都等于前两个数之和
def fib(to=10):
    curr = 0
    next = 1
    count = 0
    while count < to:
        yield curr
        curr = next
        next = curr + next
        count += 1

# 一个用法例子:每一个斐波那契数列的数加1
if __name__ == \'__main__\':
    for x in fib(20):
        print(x+1)

注意:yield 一旦被采用,那么def后面的代码会立马被认作是一个 generator 而不是一个 function,因为生成的是一个 generator,所以可以被用在 for in 这个循环语句中。而 generatorfunction 从本质上是不同的,两者的区别就涉及到了next()send()这两个函数的用法。

如果把这个python文件命名为fi.py,并运行之。那么可以瞬间得到结果:

☁  Desktop  python fi.py
1
2
3
5
9
17
33
65
129
257
513
1025
2049
4097
8193
16385
32769
65537
131073
262145

用这个方法可以自己研究很多有趣的数学问题,比如找出2万以内的质数之和,找出大于10的0次方,一次方,…,n次方的最小质数。你不需要建立一个很大的索引库,只需要一个一个数字算下去就好。

这篇Jeff的文章帮了我一些忙,但可能是我英语不好,虽然解释的很基础,但对于我来说,还是没有彻底理解yield 和与其配套的next()send()的用法,以至于我拼命想搞懂他们的用法和关系并且成功了,不过回过头来看,确实解释的很全面。我觉得这种基础全面式解释需要一个实际的用法例子来补充说明,从稍微高一点儿的地方,或者从另一个初学者的视角来解释他们,初学者并不要解释的太基础,因为初学者其实学编程语言时根本不基础,越往后学习才会涉及到编译器的某些基础概念,这也是这篇文章的立意之处。

未完待续:next()send()这两个函数的用法。