yield语句用于生成一个迭代器。函数中使用了yield之后,函数就不再是函数了,而是一个生成器,生成一个迭代器。
迭代器的特点就是每次运行时,运行到yield语句处就停下来了,下次再调用的时候,从yield语句处往下继续执行。
以生成斐波那契数列的函数fab为例,进行解释。
传统函数实现的源码如下:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
def fab(max):
n, a, b = 0, 0, 1
while n < max:
print b # 使用 yield
# print b
a, b = b, a + b
n = n + 1
for n in fab(5):
print n
上边的例子中,fab()函数执行时,每次打印一个b值,最终打印结果是
1
1
2
3
5
把代码中的print b替换成yield b之后,fab()就成了一个生成器了,就不是一个普通的函数了
#!/usr/bin/python
# -*- coding: UTF-8 -*-
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b # 使用 yield
# print b
a, b = b, a + b
n = n + 1
这里的fab()就是一个生成器,fab(5)就不再是函数调用,而是生成了一个迭代器,因此我们可以将fab(5)赋值给一个变量,并对这个变量执行的迭代器的操作,如next()。
f = fab(5)
>>> next(f) #这是Python 3的写法,Python 2的写法是f.next()
1
>>> next(f)
1
>>> next(f)
2
>>> next(f)
3
>>> next(f)
5
>>> next(f)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
每次执行f.next(),程序运行到yield b处就会返回b,并停在这里,下次再调用f.next()的时候,从上次停下来的位置继续执行后边的代码(局部变量的值保持不变),然后运行又运行至yield,返回一个b,再停下来,以此类推。
既然fab(5)现在是个迭代器,那么自然就可以用在for循环中,于是就有了下边的代码:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b # 使用 yield
# print b
a, b = b, a + b
n = n + 1
for n in fab(5):
print(n)
在上述源码中,for循环每循环一次,其实系统自动调用了一次next对fab(5)进行求值,然后通过yield返回。当迭代到头的时候,系统自动处理。
此时可能会问,这么做有什么好处?fab()函数返回一个列表行不行?为什么非要用yield把fab()弄成一个迭代器?答案是,如果把fab()函数设置成返回列表的形式,那么随着N的增大,程序所占内存会随之增大。而使用yield则不会出现这样的问题,它每执行一次返回一个值。
既然fab(5)此时已经是个生成器了,那么自然的引申到,生成器除了next()之外,还有send方法,接下来就讲讲send的用法。
把上边的fab()稍微改一下
#!/usr/bin/python
# -*- coding: UTF-8 -*-
def fab(max):
n, a, b = 0, 0, 1
while n < max:
ret = yield b # 使用 yield
# print b
if ret == "break":
print("enter break")
break
a, b = b, a + b
n = n + 1
f = fab(5)
>>>next(f)
1
>>>f.send("break")
enter break
Traceback (most recent call last):
File "/usercode/file.py", line 16, in <module>
f.send('break')
StopIteration
上边这段代码中,使用了语句ret = yield b,天真的你是不是以为ret的值就是b,奶义乌啊。如果不用send()函数,ret的值是None,如果用了send()函数,send()的参数就被传给了ret,注意,send()的参数不是传给b,而是ret。send()的执行过程是将参数传给ret,然后执行一次next()。所以上边f.send(''break'')的运行结果是enter break,即send不仅仅是把值传进去,还从上次yield停止的地方继续往下执行了,又因为在if ret == "break"中,执行了break语句,即跳出了while循环,fab()执行到了最后,所以触发了“StopIteration”,输出了后边的内容。
参考资料:https://blog.youkuaiyun.com/lxy210781/article/details/87539396