python 进阶(10) -- yield keyword


你泪如雨花洒满了纸上的天下。爱恨如写意山水画


参考

https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

这篇笔记主要是翻译了第一个链接的内容


说明

想要了解 yield 的原理,首先得要了解生成器,而生成器来自于迭代器


迭代器(Iterables)

简单来讲,当我们对一个对象可以实现遍历它的元素时(比如 list,string 这些数据类型),我们就可以说他们是可迭代的

比如下面的 list

mylist = [x*x for x in range(3)]

for i in mylist:
    print(i)

# Output:
# 0
# 1
# 4

这些可迭代的对象时一次性将所有值放入内存(memory)的,当我们的可迭代对象数据量很大或者我们只是想用其中少部分数据时,消耗就可能不是我们所想要的了

这时候,就有了生成器


生成器(Generators)

生成器是一种只能够使用一次的迭代器,比如当我们使用一个数据量庞大的 list 时,我们需要使用哪些元素,生成器才会将需要的元素放进内存中,一次只返回一个元素


Yield

Yield 是跟 return 差不多用法的关键字,不同的是它返回的是一个生成器

先看一个例子

>>> def createGenerator():
...     mylist = range(3)
...     for i in mylist:
...         yield i*i
...
>>> mygenerator = createGenerator()
>>> print(mygenerator)
<generator object createGenerator at 0x000001BFDB604830>

可以看到,函数返回的是生成器类型,继续

>>> for j in mygenerator:
...     print(j)
...

0
1
4

上面的这段例子貌似跟以前使用的差不多,但这是不存在的

现在说说自己困惑了好久的一个过程,也就是 yield 的执行过程

当一个函数或者方法在自己的 body 内发现有 yield 关键字时,里面的代码是不会执行的,只是返回一个生成器

来到 for j in mygenerator 时,这个时候才开始执行函数 body 中的循环代码,当发现 yield 时,就直接返回 i*i 的值并中断,当下次循环来到时,再从断点继续

稍微修改代码验证一下,有 IDE 的话可以打断点 debug

>>> def createGenerator():
...     mylist = range(3)
...     for i in mylist:
...         yield i*i
...         print(i)
...
>>> mygenerator = createGenerator()
>>> for j in mygenerator:
...     print(j)
...     print("hello")
...
0
hello
0
1
hello
1
4
hello
2

可以看出,print(j) 打印的是 yield i*i 返回的值,然后不会直接执行 print(i),因为这里断掉了,程序会执行 print("hello"),当下一次执行循环时,print(i)在断点处执行

yield 的原理大致是这样,不过,for j in mygenerator 这段循环还是有点不太明白,之后再想办法弄懂呗

其实,也可以使用生成器自带的方法 __next__() 来实现遍历

Note:在 python2 中,应使用 next() 方法,__next__() 是 python3 的

>>> mygenerator.__next__()
0
>>> mygenerator.__next__()
1
>>> mygenerator.__next__()
4
>>> mygenerator.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

当执行到最后再执行时,就会报 StopIteration 的错误,这个在用 for 循环的时候已经自动处理了