python协程系列(一)——生成器generator以及yield表达式详解

  参考: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    

python协程系列(一)——生成器generator以及yield表达式详解

 

   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

 

  对应的语句输出如下

python协程系列(一)——生成器generator以及yield表达式详解

 

   从上面可以看出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。还是通过几个例子来看吧!
  

  

  

上一篇:关于内核页表和进程页表的一个问题


下一篇:项目分享 | 使用MyBatis Generator自动生成数据库映射文件