asyncio
- 协程是在单线程里实现任务的切换的
- 利用同步的方式去实现异步
- 不再需要锁,提高了并发性 能
协程基础——yield
yield 和 next
先把 yield 看做 return,它首先是个 return,
- 普通的 return,就是在程序中返回某个值,返回之后程序就不再往下运行了。
看做 return 之后再把它看做一个是生成器(generator)的一部分
def foo():
print("starting...")
while True:
res = yield 4
print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))
输出
starting...
4
********************
res: None
4
-
程序开始执行以后,因为 foo 函数中有 yield 关键字,所以 foo 函数并不会真的执行,而是先得到一个生成器 g(相当于一个对象)
-
直到调用 next 方法,foo 函数正式开始执行,先执行 foo 函数中的 print 方法,然后进入 while 循环
-
程序遇到 yield 关键字,然后把 yield 想想成 return;return 了一个 4 之后,程序停止,并没有执行赋值给 res 操作,此时
next(g)
语句执行完成,所以输出的前两行(第一个是 while 上面的 print 的结果,第二个是 return 出的结果)是执行print(next(g))
的结果, -
程序执行
print("*"*20)
,输出 20 个*
-
又开始执行下面的
print(next(g))
;这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个 next 程序停止的地方开始执行的,也就是要执行 res 的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是 return 出去了,并没有给赋值操作的左边传参数),所以这个时候 res 赋值是 None,所以接着下面的输出就是res: None
, -
程序会继续在 while 里执行,又一次碰到 yield;这个时候同样 return 出 4,然后程序停止,print 函数输出的 4 就是这次 return 出的 4
可以看出:
- 带 yield 的函数是一个生成器,而不是一个函数了
- 这个生成器有一个函数就是 next 函数,next 就相当于 “下一步” 生成哪个数,这一次的 next 开始的地方是接着上一次的 next 停止的地方执行的;所以调用 next 的时候,生成器并不会从 foo 函数的开始执行,只是接着上一步停止的地方开始,然后遇到 yield 后,return 出要生成的数,此步就结束。
send
def foo():
print("starting...")
while True:
res = yield 4
print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(g.send(7))
这个例子就把上面那个例子的最后一行换掉了,输出结果:
starting...
4
********************
res: 7
4
send 函数发送一个参数给 res。
上面的例子 return 的时候,并没有把 4 赋值给 res,下次执行的时候只好继续执行赋值操作,只好赋值为 None 了,而如果用 send 的话,开始执行的时候,先接着上一次(return 4 之后)执行,先把 7 赋值给了 res,然后执行 next,遇见下一回的 yield,return 出结果后结束。
-
程序执行 g.send(7),程序会从 yield 关键字那一行继续向下运行,send 会把 7 这个值赋值给 res 变量
-
由于 send 方法中包含 next() 方法,所以程序会继续向下运行执行 print 方法,然后再次进入 while 循环
-
程序执行再次遇到 yield 关键字,yield 会返回后面的值后,程序再次暂停,直到再次调用 next 方法或 send 方法。
抛出错误
当生成器运行到底后,将抛出 StopIteration
错误,若生成器有返回值,则返回值存储在异常对象的 value 属性中
def coroutine_example(name):
print('start coroutine...name:', name)
while True:
x = yield name # 调用next()时,产出yield右边的值后暂停;调用send()时,产出值赋给x,并往下运行
if x is None:
return 'zhihuID: Zarten'
print('send值:', x)
coro = coroutine_example('Zarten')
next(coro)
print('send的返回值:', coro.send(6))
try:
coro.send(None)
except StopIteration as e:
print('返回值:', e.value)
yield from
yield from
后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。
简单应用:拼接可迭代对象
我们可以用一个使用 yield
和一个使用 yield from
的例子来对比看下。
使用 yield
# 字符串
astr='ABC'
# 列表
alist=[1,2,3]
# 字典
adict={"name":"wangbm","age":18}
# 生成器
agen=(i for i in range(4,8))
def gen(*args, **kw):
for item in args:
for i in item:
yield i
new_list=gen(astr, alist, adict, agen)
print(list(new_list))
# ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
使用 yield from
# 字符串
astr='ABC'
# 列表
alist=[1,2,3]
# 字典
adict={"name":"wangbm","age":18}
# 生成器
agen=(i for i in range(4,8))
def gen(*args, **kw):
for item in args:
yield from item
new_list=gen(astr, alist, adict, agen)
print(list(new_list))
# ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]
由上面两种方式对比,可以看出,yield from 后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个地 yield 出来,对比 yield 来说代码更加简洁,结构更加清晰。
复杂应用:生成器的嵌套
如果你认为只是 yield from
仅仅只有上 述的功能的话,那你就太小瞧了它,它的更强大的功能还在后面。
当 yield from
后面加上一个生成器后,就实现了生成器的嵌套。
当然实现生成器的嵌套,并不是一定必须要使用
yield from
,而是使用yield from
可以让我们避免让我们自己处理各种料想不到的异常,而让我们专注于业务代码的实现。
如果自己用 yield
去实现,那只会加大代码的编写难度,降低开发效率,降低代码的可读性。既然 Python 已经想得这么周到,我们当然要好好利用起来。
讲解它之前,首先要知道这个几个概念
调用方
:调用委托生成器的客户端(调用方)代码委托生成器
:包含 yield from 表达式的生成器函数子生成器
:yield from 后面加的生成器函数
以下这个例子,是实现实时计算平均值的。 比如,第一次传入 10,那返回平均数自然是 10. 第二次传入 20,那返回平均数是 (10+20)/2=15;第三次传入 30,那返回平均数 (10+20+30)/3=20
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
count += 1
total += new_num
average = total/count
# 委托生成器
def proxy_gen():
while True:
yield from average_gen()
# 调用方
def main():
calc_average = proxy_gen()
next(calc_average) # 预激下生成器
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
if __name__ == '__main__':
main()
认真阅读以上代码,应该很容易能理解,调用方、委托生成器、子生成器之间的关系。
委托生成器的作用是:在调用方与子生成器之间建立一个双向通道
。
所谓的双向通道是什么意思呢? 调用方可以通过 send()
直接发送消息给子生成器,而子生成器 yield 的值,也是直接返回给调用方。
直到这里,委托生成器的优势还没显现出来,因为你完全可以直接调用 average_gen()
获取生成器,实现一模一样的效果。
你可能会经常看到有些代码,还可以在 yield from
前面看到可以赋值。这是什么用法?