最近在学习Python中生成器时,遇到了一个yield关键词,廖雪峰老师的官网中也没有详细的解释,经过一番查阅和研究,终于对它有了一些认识并做了总结(如有不对之处,还请大神指正)。

首先先简单了解下生成器generator,它是为了弥补类似list生成序列时造成的内存空间浪费,例如下面代码中L会将所有值运算出来,全部放到内存中,可想而知,要是有百万千万级的数据,该占用多大内存。而使用生成器的形式,只要将[]改为(),这样只有需要用到的时候,才会去计算下一个值。

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x0062B568>

如何取出g中的值,只要调用next()就可以了

>>> next(g)
0
>>> next(g)
1

接下来进入正题,我们通过一个实战来讲下yield关键字,让你完全攻克它

 def foo(num):
print("foo starting")
while num < 5:
res = yield num
num = num + 1
print('res ::',res) print("Starting First")
g = foo(0) print("Starting for")
for n in g:
print('n ::',n)

首先先看下程序执行顺序,按照一般函数理解,3个starting的顺序应该是 Starting First --> foo starting --> Starting for,但执行结果如下

 Starting First
Starting for
foo starting
n :: 0
res :: None
n :: 1
res :: None
n :: 2
res :: None
n :: 3
res :: None
n :: 4
res :: None

那就奇怪了,调用foo()函数动作在for循环之前啊,那么在这里要明确一下,函数中含有yield关键字,那么就不能把它当做一个普通函数了,而是一个生成器,且不会立即执行,只有调用next()时才会执行。

继续往下看,打印出“Starting for” 之后就进入了foo函数中执行了,也就是进入g生成器中了,可是上面讲过,只有调用next()才会到生成器中取值啊,为什么 for n in g 也会进去呢?

那就要研究下for n in g这段语句了,其实Python的for循环本质上就是通过不断调用next()函数实现的,也就是说如下两种写法是完全等价的。

 listx = [1,2,3,4,5]

 # 循环方法一
for n in listx:
print(n) # 等价方法二
glistx = iter(listx) # 转换为Iterator对象 while True:
try:
# 获得下一个值:
x = next(glistx)
print(x)
except StopIteration:
# 遇到StopIteration就退出循环
break

这样就显而易见了,其实for循环中也是调了next()函数,到生成器中去取值的。

继续往下走,终于到了梦寐以求的yield关键字,怎么理解呢?首先可以先按照return来理解,所以当num=0的时候,走到yield num就直接return了,因此打印出 n :: 0

继续执行for循环,继续调用next()函数,那么这里就要了解一个知识点,生成器中的next()是接着上次return(yield)的地方继续执行,而不是从头执行,这就是yield与return的区别。此时,由于上一步yield已经返回了0到for循环了,那么res就没有变量来赋值了,也就是None,因此下一步打印的就是res :: None,此时num=num+1变为了1。在while True中循环,又遇到了yield num,那么又返回给了for函数一个1,因此 n接收到了1,打印出n :: 1,后面的以此类推,不再赘述。

根据上面步骤解读,应该已经明白了yield,next()的含义和用法了吧,那我们再乘胜追击,在for循环中再加个send()函数,这又是什么含义呢?

 def foo(num):
print("foo starting")
while num < 5:
res = yield num
num = num + 1
print('res ::',res) print("Starting First")
g = foo(0) print("Starting for")
for n in g:
g.send(n + 100)
print('n ::',n)

其实可以这样理解,yield num返回之后,程序下一步是赋值给res,只不过yield返回后变为空了,所以res被赋值None,而send就是解决赋值的问题。send()含义就是继续执行yield之后的赋值操作,也就是重新赋值给res,那么再打印res的话就不是None了,上述执行结果如下:

 Starting First
Starting for
foo starting
res :: 100
n :: 0
res :: None
res :: 102
n :: 2
res :: None
res :: 104
Traceback (most recent call last):
File "d:/VSCode/Python/genaratex.py", line 13, in <module>
g.send(n + 100)
StopIteration

按照如上思路,先赋值res=100,再打印res,再打印n。下个循环后res还是应该先赋值101啊,为什么结果是res :: None 和 res :: 102 呢,这里又有一个知识点,send函数中不仅仅是赋值,并且自带next()函数调用,所以在send内部就执行了一次next(),导致num再次加1,再次执行了yield num,只是for函数里面没有接收变量罢了,因此就会出现打印出res :: None和 res :: 102的输出了。

如此,代码最后的报错也应该知道为什么了吧,即当n获取生成器中已经是最后一个值的时候,send中再次next(),当然找不到值了,触发了StopIteration异常。

总结:

  1. 生成器generator可以用()来定义,也可以使用iter()来转换,带yield的函数默认都是generator
  2. next()取值时,除首次外都是从上次yield的地方开始执行的
  3. Python中for循环的本质也是调用next()来逐个取值
  4. send函数是先在yield处进行赋值,再进行next()操作,如果没有进行yield,直接调用send会报错找不到需要赋值的对象。

一场由yield引发的连串拷问的更多相关文章

  1. 一场由fork引发的超时,让我们重新探讨了Redis的抖动问题

    摘要:一次由fork引发的时延抖动问题. 背景介绍 华为云数据库GaussDB(for Redis) 是一款基于计算存储分离架构,兼容Redis生态的云原生NoSQL数据库:它依靠共享存储池实现了强一 ...

  2. hibernate性能消耗太狠了。果断减肥引发的连串意外惊喜

    近期在云服务器上新部署了一个项目 硬件配置 CPU: 2核    内存: 4096 MB (I/O优化) 开始是调试测试在用 没发觉,今天我看了下监控 cpu使用率达到了60-70% 而且一直持续 我 ...

  3. 一场由like引发的事故

    故事背景: 有一张用户级表,数据量在千万级别,而运营人员要查看这张表,其中有一项查询条件为根据“错误类型”(单值)查出所有包含这个类型的数据,而这个数据类型在数据库存放的方式类似于 “1,2,3,4, ...

  4. .NET平台开源项目速览(11)KwCombinatorics排列组合使用案例(1)

    今年上半年,我在KwCombinatorics系列文章中,重点介绍了KwCombinatorics组件的使用情况,其实这个组件我5年前就开始用了,非常方便,麻雀虽小五脏俱全.所以一直非常喜欢,才写了几 ...

  5. Day037--Python--线程的其他方法,GIL, 线程事件,队列,线程池,协程

    1. 线程的一些其他方法 threading.current_thread()  # 线程对象 threading.current_thread().getName()  # 线程名称 threadi ...

  6. Promise和Generator

    异同: 1.promise解决的是串行的嵌套异步问题. 2.yield把Generator Function切割为有多个出口的Generation. 3.Promise是社区的研发产物,yield是E ...

  7. rest framework serializer

    串行器 扩大串行的用处是什么,我们想地址.然而,这不是一个简单的问题,它会采取一些严重的设计工作. -罗素基思-马吉,Django的用户组 串行器允许诸如查询集和模型实例复杂的数据转换为原生的Pyth ...

  8. CDR

    伴随着新经济.独角兽一同被热议的,中国将很快推出存托凭证迎接独角兽回归.中国存托凭证(CDR)已成为当下热门话题.说不清CDR,还能和小伙伴们愉快地聊天吗? CDR到底是什么?它具有哪些优势?能否带来 ...

  9. [每日一题]面试官问:Async/Await 如何通过同步的方式实现异步?

    关注「松宝写代码」,精选好文,每日一题 ​时间永远是自己的 每分每秒也都是为自己的将来铺垫和增值 作者:saucxs | songEagle 一.前言 2020.12.23 日刚立的 flag,每日一 ...

随机推荐

  1. Java实现 LeetCode 507 完美数

    507. 完美数 对于一个 正整数,如果它和除了它自身以外的所有正因子之和相等,我们称它为"完美数". 给定一个 整数 n, 如果他是完美数,返回 True,否则返回 False ...

  2. Java实现 LeetCode 326 3的幂

    326. 3的幂 给定一个整数,写一个函数来判断它是否是 3 的幂次方. 示例 1: 输入: 27 输出: true 示例 2: 输入: 0 输出: false 示例 3: 输入: 9 输出: tru ...

  3. Java实现 蓝桥杯VIP 算法提高 P0404

    计算一个无符号整数的阿尔法乘积.对于一个无符号整数x来说,它的阿尔法乘积是这样来计算的:如果x是一个个位数,那么它的阿尔法乘积就是它本身:否则的话,x的阿尔法乘积就等于它的各位非0的数字相乘所得到的那 ...

  4. java实现第五届蓝桥杯信号匹配

    信号匹配 从X星球接收了一个数字信号序列. 现有一个已知的样板序列.需要在信号序列中查找它首次出现的位置.这类似于串的匹配操作. 如果信号序列较长,样板序列中重复数字较多,就应当注意比较的策略了.可以 ...

  5. java关键字tranisent用法详解

    作为java中的一个关键字,tranisent用的并不是很多,但是在某些关键场合,却又起着极为重要的作用,因此有必要对它进行一些必要的了解. 一.定义:声明不用序列化的成员域.(源自百度百科) 二.作 ...

  6. tensorflow2.0学习笔记第二章第四节

    2.4损失函数损失函数(loss):预测值(y)与已知答案(y_)的差距 nn优化目标:loss最小->-mse -自定义 -ce(cross entropy)均方误差mse:MSE(y_,y) ...

  7. iOS-Swift版本自定义CStextView的实现

    CSTextView继承自UITextView,并为其增加了placeHolder属性,支持代码和SB方式创建:demo实现了CSTextView的自动排版 效果图 CSTextView地址: htt ...

  8. 简单说维特比算法 - python实现

    动态规划求最短路径算法,与穷举法相比优点在于大大降低了时间复杂度; 假如从起点A到终点S的最短路径Road经过点B1,那么从起点A到B1的最短路径的终点就是B1,否则如果存在一个B2使得A到B2的距离 ...

  9. PyQt5 对话框示例

    import sys from PyQt5.QtWidgets import * from PyQt5.QtGui import * class Example(QMainWindow): def _ ...

  10. DedeCms 首页、列表页调用文章body内容的方法

    [第一种方法] arclist标签使用如下: {dede:arclist row='1' typeid='1' addfields='body' idlist='1' channelid='1'} [ ...