最近在学习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. WEB APPLICATION PENETRATION TESTING NOTES

    此文转载 XXE VALID USE CASE This is a nonmalicious example of how external entities are used: <?xml v ...

  2. 【RT-Thread笔记】OneNet软件包的使用

    去年,RT-Thread发布了RT-Thread Studio初版RT-ThreadStudio的使用体验,经过不断更新迭代之后,来到了V1.1.0,咱也来拥抱一下新版本. 本篇笔记咱们以接入OneN ...

  3. Rocket - interrupts - Parameters

    https://mp.weixin.qq.com/s/eD1_hG0n8W2Wodk25N5KnA 简单介绍interrupts相关的Parameters. 1. IntRange 定义一个中断号区间 ...

  4. Chisel3 - util - Lookup

    https://mp.weixin.qq.com/s/g85Si6n37D9PYfR5hEoRQQ     实现一个查找逻辑.   参考链接: https://github.com/freechips ...

  5. Java实现 蓝桥杯 算法训练 数据交换

    试题 算法训练 数据交换 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 编写一个程序,输入两个整数,分别存放在变量x和y当中,然后使用自己定义的函数swap来交换这两个变量的值. ...

  6. PAT 科学计数法

    科学计数法是科学家用来表示很大或很小的数字的一种方便的方法,其满足正则表达式 [+-][1-9].[0-9]+E[+-][0-9]+,即数字的整数部分只有 1 位,小数部分至少有 1 位,该数字及其指 ...

  7. Java多线程之深入解析ThreadLocal和ThreadLocalMap

    ThreadLocal概述 ThreadLocal是线程变量,ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的.ThreadLocal为变量在每个线程中都创建了一个副本,那 ...

  8. python3 优惠券查询GUI程序

    from tkinter import ttkfrom tkinter import messageboximport pymssqlimport tkinterimport decimalimpor ...

  9. JDK8在泛型类型推导上的变化

    概述 JDK8升级,大部分问题可能在编译期就碰到了,但是有些时候比较蛋疼,编译期没有出现问题,但是在运行期就出了问题,比如今天要说的这个话题,所以大家再升级的时候还是要多测测再上线,当然JDK8给我们 ...

  10. css布局相关:涉及到常见页面样式难点

    一.display:table用法 Table:display:tableBody:table-row-group;Tr: table-row;Td: table-cell https://www.c ...