协程是针对单个cpu的,因为协程运行在单线程内,可以使用协程实现类似多并发的任务,并且在单cpu时,效率一般要比协程高。
2 协程的实现
使用yield 关键字
-
-
yield 有两个关键作用
-
返回一个值
-
接收调用方传入的值,且不影响返回值
3 例子
def consume(): r = '' while True: n = yield r # 断点 if not n: return print('消费者 正在消费: {}'.format(n)) r = '200 RMB' def produce(c): c.send(None) # 启动生成器 n = 0 while n < 5: n += 1 print('生产者 正在生产: {}'.format{n}) r = c.send(n) print('[生产者] 消费者返回: {}'.format{r}) print('------------') c.close() c = consume() produce(c)
4 分析
yield 是实现生成器的重要关键字。
def my_generator(n): for i in range(n): temp = yield i print(f'我是{temp}') g=my_generator(5) print(next(g)) #输出0, 打印结果,我是None print(next(g)) #输出1 g.send(100) #temp的打印结果为100 print(g.send(100)) #输出2 print(next(g)) #输出3 print(next(g)) #输出4
特点:
-
延迟加载特性,生成器需要启动,如果用send启动,第一次需要传入None,否则报错
-
使用send()方法传进去的值,实际上就是yield表达式返回的值,没有传值则默认返回None
-
如果使用了send(value),传递进去的那个value会取代那个表达式的值,并且会将传递进去的那个值返回给yield表达式的结果temp,而send(value)的返回值就是原来的值
-
每次惰性返回一个值
send(value)是有返回值的,即迭代的那个值。
send 的主要作用, 当需要手动更改生成器里面的某一个值并且使用它,则send发送进去一个数据,然后保存到yield语句的返回值,以提供使用;send(value)的返回值就是那个本来应该被迭代出来的那个值。这样既可以保证我能够传入新的值,原来的值也不会弄丢。
throw 方法:
在生成器中抛出异常,并且这个throw函数会返回下一个要迭代的值或者是StopIteration。
def my_generator(): yield 'a' yield 'b' yield 'c' yield 'd' g=my_generator() print(next(g)) print(next(g)) print('-------------------------') print(g.throw(StopIteration)) print(next(g)) '''运行结果为: a b ------------------------- StopIteration
触发异常之后,返回StopIteration,同时迭代终止,而StopIteration作为被传入的yield值,会顺位进入下一次迭代,即把后置位的 ‘c' 覆盖掉。
def my_generator(): while True: try: yield 'a' yield 'b' yield 'c' yield 'd' yield 'e' except ValueError: print('触发“ValueError"了') except TypeError: print('触发“TypeError"了') g=my_generator() print(next(g)) print(next(g)) print('-------------------------') print(g.throw(ValueError)) print('-------------------------') print(next(g)) print(next(g)) print('-------------------------') print(g.throw(TypeError)) print('-------------------------') print(next(g))
加入while 循环之后,循环会执行完成所有调用的 yield, throw时也会执行一个yield,而throw掉的异常被捕获了,所以就不会覆盖掉 ’a‘,a顺位进入下一次迭代,这就是a在 分割线之间的原因了。
生成器启动时,使用next可以直接启动并调用,但是使用 send() 第一次要传入 None,不然会报错。
close() 表示关闭生成器,如果后续再调用此生成器,那么会抛出异常。
生成器的终止- StopIteration
在一个生成器中,如果没有return,则默认执行到函数完毕返回 StopIteration;
如果遇到 return,则直接抛出 StopIteration,如果 return 后面有值,会一并返回。
可以使用 except StopIteration as e 捕获异常,通过e.value 来获取值。
5 协程的状态查看
协程分别有四种状态,可以导入 inspect.getgeneratorstate() 模块来查看
GEN_CREATED: 被创建,等待执行
GEN_RUNNING: 解释器执行
GEN_SUSPENDED: 在 yield 表达式处暂停
GEN-CLOSED: 执行结束
6 协程的个人理解
从某些角度来说,协程其实就是一个可以暂停执行的函数,并且可以继续执行。那么 yield 已经可以暂停执行了,如果在暂停后有办法把一些 value 发送到暂停执行的函数中,那么这就是 Python 中的协程。
不足之处:协程函数的返回值不是特别方便获取,比如 return 的返回值需要捕获异常as e
,使用e.value来获得