048 Python里面yield的实现原理
python 生成器yield的总结
yeild的原理:一个带有yield的函数就是一个generator生成器(python 的 generator 只保留栈帧上下文,不保留调用栈,而且 generator 函数不允许 return;),和普通的函数不一样。只有调用next()函数的时候才会执行函数语句,在for循环中会自动调用next()方法。函数执行过程中遇到一个yield会中断一次,返回一个迭代值,函数保存自己的变量和状态,下次迭代时(也就是下次next()的时候)从yield下一条语句继续执行(这个性质很重要),函数恢复之前状态,直到遇到下一个yield返回迭代值,这样循环。
(1):通常的for..in...循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。他可以是a = [1,2,3],也可以是a = [x*x for x in range(3)]。
它的缺点也很明显,就是所有数据都在内存里面,如果有海量的数据,将会非常耗内存。
(2)生成器是可以迭代的,但是只可以读取它一次。因为用的时候才生成,比如a = (x*x for x in range(3))。!!!!注意这里是小括号而不是方括号。
(3)生成器(generator)能够迭代的关键是他有next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。
(4)带有yield的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代
(5)yield是一个类似return 的关键字,迭代一次遇到yield的时候就返回yield后面或者右面的值。而且下一次迭代的时候,从上一次迭代遇到的yield后面的代码开始执行
(6)yield就是return返回的一个值,并且记住这个返回的位置。下一次迭代就从这个位置开始。
(7)带有yield的函数不仅仅是只用于for循环,而且可用于某个函数的参数,只要这个函数的参数也允许迭代参数。
(8)send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式值。
(9)send()和next()都有返回值,他们的返回值是当前迭代遇到的yield的时候,yield后面表达式的值,其实就是当前迭代yield后面的参数。
(10)第一次调用时候必须先next()或send(),否则会报错,send后之所以为None是因为这时候没有上一个yield,所以也可以认为next()等同于send(None)
深入理解 Python yield
https://blog.csdn.net/lftaoyuan/article/details/78915518
python2和python3是不兼容的,通篇环境都是python3.6
简单的yield实例
以前只是粗略的知道yield
可以用来为一个函数返回值塞数据,比如下面的例子:
def addlist(alist):
for i in alist:
yield i + 1
取出alist
的每一项,然后把i + 1
塞进去。然后通过调用取出每一项:
alist = [1, 2, 3, 4]
for x in addlist(alist):
print(x)
这的确是yield
应用的一个例子,但是,看过很多东西,并自己反复体验后,对yield有了一个全新的理解,其中这篇算是精品了。
包含yield的函数
假如你看到某个函数包含了yield
,这意味着这个函数已经是一个Generator
,它的执行会和其他普通的函数有很多不同。比如下面的简单的函数:
def h():
print('study yield')
yield 5
print('go on!')
h()
可以看到,调用h()
之后,print 语句并没有执行!这就是yield
。具体的内容后面会越来越清晰,包括yield
的工作原理。
yield是一个表达式
python 2.5以前,yield
是一个语句,我也没有考证,因为早都不用了,现在yield
是一个表达式:
m = yield 5
表达式(yield 5)的返回值将赋值给m,所以,m = 5
肯定是错的。
那么如何获取(yield 5)的返回值呢?需要用到send(msg)
。
yield工作原理
揭晓yield
的工作原理,需要配合next()
函数。上面的h()
被调用后并没有执行,因为它有yield
表达式,通过next()
可以恢复Generator
执行,直到下一个yield
。
def h():
print('study yield')
yield 5
print('go on!')
c = h()
d1 = next(c) # study yield
d2 = next(c)
"""
study yield
go on!
Traceback (most recent call last):
File "D:/idea/workspace/pythonSpace/PythonDemo/static/yield_demo.py", line 35, in <module>
d2 = next(c)
StopIteration
"""
- 1
- 2
- 3
- 4
next()`被调用后,`h()`开始执行,直到遇到`yield 5
因此输出结果是:study yield
当我们再次调用next()
时,会继续执行,直到找到下一个yield
。由于后面没有yield
了,因此会拋出异常:
study yield
go on!
Traceback (most recent call last):
File "D:/idea/workspace/pythonSpace/PythonDemo/static/yield_demo.py", line 35, in <module>
d2 = next(c)
StopIteration
send(msg) 与 next()
了解了next()
如何让包含yield
的函数执行后,我们再来看另外一个非常重要的函数send(msg)
。
其实next()
和send()
在一定意义上作用是相似的
区别
send()
可以传递yield
的值
next()
只能传递None
。
所以next()
和 send(None)
作用是一样的。
def s():
print('study yield')
m = yield 5
print(m)
d = yield 16
print('go on!')
c = s()
s_d = next(c) # 相当于send(None)
c.send('Fighting!') # (yield 5)表达式被赋予了'Fighting!'
输出的结果为:
study yield
Fighting!
注意 生成器刚启动时(第一次调用),请使用next()
语句或是send(None)
,不能直接发送一个非None的值,否则会报TypeError
,因为没有yield
语句来接收这个值。
send(msg) 与 next()的返回值
send(msg)
和 next()
的返回值比较特殊,是下一个yield
表达式的参数(yield 5,则返回 5)。
到这里,第一个例子中,通过for i in alist
遍历 Generator
,其实是每次都调用了next()
,而每次next()
的返回值正是yield
的参数:
def s():
print('study yield')
m = yield 5
print(m)
d = yield 16
print('go on!')
c = s()
s_d1 = next(c) # 相当于send(None)
s_d2 = c.send('Fighting!') # (yield 5)表达式被赋予了'Fighting!'
print('My Birth Day:', s_d1, '.', s_d2)
输出结果:
study yield
Fighting!
My Birth Day: 5 . 16
中断Generator
上面的例子中,当没有可执行程序的时候,会抛出一个StopIteration
, 开发过程中,中断Generator是一个非常灵活的技巧
throw
通过抛出一个GeneratorExit异常来终止Generator。
close
close的作用和throw是一样的,看它的源码,可以发现,它和raise一球样
def throw(self, type, value=None, traceback=None):
'''Used to raise an exception inside the generator.'''
# 用于在生成器中抛出一个异常。
pass
def close(self):
'''Raises new GeneratorExit exception inside the generator to terminate the iteration.'''
# 在生成器中生成新的GeneratorExit异常来终止迭代。
pass
其实最后一个中断生成器可以忽略的,在开发过程中,不可避免的要用到这些,但是Python3内部已经做得很好了,一般不太需要手动去做这件事情。
demo地址
https://github.com/seeways/PythonDemo/blob/master/static/yield_demo.py