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
"""
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
throw()
用来向生成器函数送入一个异常,可以结束系统定义的异常,或者自定义的异常。我的理解是相当于send()进去一个错误类型,然后抛出异常。我的理解可能不准确,仅供参考。
def generator3():
num = yield 1
print(num)
try:
yield 2
except ValueError:
print("捕获异常:ValueError")
yield 3
if __name__ == "__main__":
gen = generator3()
print(gen.send(None))
print(gen.send(101))
gen.throw(ValueError)
gen.close()
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内部已经做得很好了,一般不太需要手动去做这件事情。
1.yield from
我们先来看个例子:
def test03(iterabld):
yield iterabld
def test04(iterable):
yield from iterable
if __name__ == "__main__":
for iterabld in test03(range(5)):
print(iterabld)
for value in test04(range(5)):
print (value)
返回值:
# range(0, 5)
# 0
# 1
# 2
# 3
# 4
这两个的返回值是一样的,可以看的出来,yield form 将可迭代对象中的值直接迭代出来了。下边再看一个例子:
def generator01():
num = yield 1
print(num)
yield 2
yield 3
return 4
def test01(gen):
ll = yield from gen()
print(ll)
def main():
gen = test01(generator01)
print(gen.send(None))
print(gen.send(101))
print(gen.throw(ValueError))
if __name__ == "__main__":
main()
执行下例子,可以发现,再main()中的send()和throw()都是直接作用
到生成器generator01中的。这其中main()函数被称作调用方
,test01()被称作委托生成器
(包含yield from表达式的生成器函数),generator01()被称作子生成器
(yield from后面加的生成器函数)。委托生成器的作用是:在调用方与子生成器之间建立一个双向通道
,拿例子来讲,就是main()中的send()等直接作用到generator01到,而generator01中yield产生的值直接返回到main()中。
委托生成器中:ll = yield from gen()
为啥会赋值? 这是接收的return的值。生成器没有yield,有return的时候,会抛出StopIteration异常,在抛出StopIteration的异常的时候,会将return的值赋给ll。
注:1. yield from 是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。 2.yield from 后面需要加的是可迭代对象。它可以是普通的可迭代对象,也可以是迭代器、生成器。
2. async/await
async/await 是用是python3.5后出来的协程异步编程的API, 是为了区分yield,yield from生成器,而使语义更加明确。以下示例代码只是为了展示await与yield from对比,实际开发中,不要这么做!不要这么做!!!
# 示例代码一
import requests
async def request(url):
return requests.get(url)
async def spider(url):
return await request(url)
if __name__ == '__main__':
sp = spider("http://www.baidu.com")
try:
print(sp.send(None))
except StopIteration as e:
print (e.value)
# 示例代码二
import requests
import types
@types.coroutine
def request(url):
yield requests.get(url)
async def spider(url):
return await request(url)
if __name__ == '__main__':
sp = spider("http://www.baidu.com")
print(sp.send(None))
# 以上代码的打印结果都是 <Response [200]>
async def
语法定义协程函数
,在之前这个功能是通过装饰器实现的。但是这样定义的协程函数中不能使用yield语句
,只允许使用return或await语句返回数据。await
的使用场景与yield from
类似,但是await
接收的对象不同。yield from
可以是任意的可迭代对象。而await
接收的对象必须是可等待对象(awaitable object)
注:1. async/await是在python3.5版么以及之后的版本中才能使用。2. async不能和yield同时使用。3.await只能作用于可等待对象
个人的感想:async/await的出现是为了协程,是为了区分生成器使编程更加明确,来提升Python中的异步编程体验。具体的使用将在接下来的asyncio的介绍中,将大量使用async/await。
参考大佬: