参考:https://blog.csdn.net/qq_27825451/article/details/85226239
声明:本文将详细讲解python协程的实现机理,为了彻底的弄明白它到底是怎么一回事,鉴于篇幅较长,将彻底从最简单的yield说起从最简单的生成器开始说起,因为很多看到这样一句话的时候很懵,即“yield也是一种简单的协程”,这到底是为什么呢?本次系列文章“python协程系列文章”将从最简单的生成器、yield、yield from说起,然后详细讲解asyncio的实现方式。本文主要讲解Python的生成器的各种详细操作,以及yield表达式的详细应用。
一,生成器generator详解
注意:关于什么是可迭代对象、什么是生成器、什么是迭代器这里不再赘述。
可以参考:https://www.cnblogs.com/minseo/p/15357586.html
yield是实现生成器的重要关键字,但是yield语句有一些非常重要的小细节需要注意,可能我们在写一个简单的生成器的时候有很多东西没有用到,这里将分情况逐一介绍。特别是生成器的三个重要方法,一个是next(),一个是send(),一个是throw(),他们到底有什么样的作用。
1,最简单的生成器
test.py
# 最简单的生成器 # 该生成器函数传递一个整数,然后遍历,生成器输出为0,1,2,3...n-1 def my_generator(n): for i in range(n): yield i for i in my_generator(5): print(i,end=' ') # 0 1 2 3 4
2,send()方法使用
# send()方法的使用 def my_generator(n): for i in range(n): temp = yield i print('我是%s,是通过send方法传递的参数' %(temp)) g = my_generator(5) # 初始化生成器,相当于send(None) # 如果使用send(None)初始化,第一次传递的参数必须为None # 第一次迭代输出0 print(next(g)) # 0 # 第二次迭代返回值为1 运行yield后代码输出temp的值,因为没有传递使用为None print(next(g)) # 我是None,是通过send方法传递的参数 # 1 # 传递参数100,执行打印输出temp的值 # send也是有返回值的,为yield后的值,这里没有使用print输出 g.send(100) # 我是100,是通过send方法传递的参数 # 第四次迭代返回值为3 运行yield后代码输出temp的值,因为没有传递使用为None print(next(g)) # 3 # 我是None,是通过send方法传递的参数 # 第五次迭代,执行到yield停止了,循环结束了,没有执行到print这步 print(next(g)) # 4
对应的语句输出如下
从上面可以看出yield语句与普通函数的return语句的区别在哪里了,主要集中在以下几点
(1)return不能写成“temp=return xxx”的形式,会提示语法错误,但是yield可以写成"temp=yield xxx"的形式
(2)普通函数return后面的语句都是不会再执行的,但是yield语句后面的依然会执行,但是需要注意的是,由于“延迟加载”特性,yield后面的代码并不是在第一次迭代的时候执行的,而是在第二次迭代的时候才执行第一次yield后面没有执行的代码。也正是这个特性,构成了yield为什么是实现协程最简单的实现。
个人备注:如果在生成器函数中定义return则遇到return则会报StopIteration错误立即退出生成器
(3)使用send()方法传进去的值,实际上就是yield表达式返回的值,这就是为什么前面每次输出print(temp)都打印出None,因为没有send值,所以temp为None,但是send(100)之后却打印100,因为此时temp就是100了。
个人备注:使用send()方法传递进去的值是赋值给yield前面的变量temp了,yield的返回值是yield后的值,这个返回值也是send()方法的返回值,即send()方法接收的返回值为yield的返回值
我甚至还可以在yield后面不放任何东西,如下代码:
# send()方法的使用 # yield不设置返回值 def my_generator(n): for i in range(n): temp = yield print('我是%s,是通过send方法传递的参数' %(temp)) g = my_generator(5) print(next(g)) print(next(g)) g.send(100) print(next(g)) print(next(g)) # None # 我是None,是通过send方法传递的参数 # None # 我是100,是通过send方法传递的参数 # 我是None,是通过send方法传递的参数 # None # 我是None,是通过send方法传递的参数 # None
3,yield语句的用法总结
yield的一般形式为:
temp=yield 表达式(每次迭代要返回的值)
(1)如果要返回确定的值,后面的表达式不可省略,绝大部分情况下我们也不省略,否则只能返回None
(2)如果使用了send(value),传递进去的那个value会取代那个表达式的值,并且会将传递进去的那个值返回给yield表达式的结果temp,所以如果想要在yield后面使用传递进去的那个值,必须要有使用temp,否则无法使用
(3)yield语句的一般形式
temp = yield expression(推荐:既可以返回迭代的值,也可以接受send进去的参数并使用) yield expression(也可以使用:只不过不能接受send传递的值) temp = yield(不推荐 yield没有设置返回值) yield(不推荐)
4,迭代器(生成器)的send()方法详解
主要目的是交互
查看send的定义,得到send(arg)是有返回值的,而且他的返回值就是原本我一个迭代处理的那个值,如下所示
# send的返回值 def my_generator(n): for i in range(n): yield i g=my_generator(5) print(next(g)) print(next(g)) g.send(100) print(next(g)) print(next(g)) # 0 # 1 # 3 # 4
我们发现虽然100传进去了,但是他并没有迭代出来,那原来的2去哪里了呢?send(100)实际上就是返回的2
如果改为以下代码获取send的返回值
# send返回值2 def my_generator(n): for i in range(n): yield i g=my_generator(5) print(next(g)) print(next(g)) a = g.send(100) print('我是send的返回值%s' %(a)) print(next(g)) print(next(g)) # 0 # 1 # 我是send的返回值2 # 3 # 4
send(arg)方法总结
(1)它的主要作用是,单位需要手动更改生成器成某一个值并且使用它,则send发送进去一个暑假,然后报错到yield语句的返回值,以提供使用。
(2)send(arg)的返回值就是那个本来应该被迭代出来的那个值。这样既可以保证我能传入新的值,原来的值也不会弄丢。
5,生成器的throw方法用法
这个函数相比较于前面的next()、send()来说更加复杂,先看一下它的函数描述:
raise exception in generator,return next yielded value or StopIteration,即在生成器中抛出异常,并且这个throw函数会返回下一个要迭代的值或者是StopIteration。还是通过几个例子来看吧!