一场由yield引发的连串拷问
最近在学习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异常。
总结:
- 生成器generator可以用()来定义,也可以使用iter()来转换,带yield的函数默认都是generator
- next()取值时,除首次外都是从上次yield的地方开始执行的
- Python中for循环的本质也是调用next()来逐个取值
- send函数是先在yield处进行赋值,再进行next()操作,如果没有进行yield,直接调用send会报错找不到需要赋值的对象。
一场由yield引发的连串拷问的更多相关文章
- 一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
摘要:一次由fork引发的时延抖动问题. 背景介绍 华为云数据库GaussDB(for Redis) 是一款基于计算存储分离架构,兼容Redis生态的云原生NoSQL数据库:它依靠共享存储池实现了强一 ...
- hibernate性能消耗太狠了。果断减肥引发的连串意外惊喜
近期在云服务器上新部署了一个项目 硬件配置 CPU: 2核 内存: 4096 MB (I/O优化) 开始是调试测试在用 没发觉,今天我看了下监控 cpu使用率达到了60-70% 而且一直持续 我 ...
- 一场由like引发的事故
故事背景: 有一张用户级表,数据量在千万级别,而运营人员要查看这张表,其中有一项查询条件为根据“错误类型”(单值)查出所有包含这个类型的数据,而这个数据类型在数据库存放的方式类似于 “1,2,3,4, ...
- .NET平台开源项目速览(11)KwCombinatorics排列组合使用案例(1)
今年上半年,我在KwCombinatorics系列文章中,重点介绍了KwCombinatorics组件的使用情况,其实这个组件我5年前就开始用了,非常方便,麻雀虽小五脏俱全.所以一直非常喜欢,才写了几 ...
- Day037--Python--线程的其他方法,GIL, 线程事件,队列,线程池,协程
1. 线程的一些其他方法 threading.current_thread() # 线程对象 threading.current_thread().getName() # 线程名称 threadi ...
- Promise和Generator
异同: 1.promise解决的是串行的嵌套异步问题. 2.yield把Generator Function切割为有多个出口的Generation. 3.Promise是社区的研发产物,yield是E ...
- rest framework serializer
串行器 扩大串行的用处是什么,我们想地址.然而,这不是一个简单的问题,它会采取一些严重的设计工作. -罗素基思-马吉,Django的用户组 串行器允许诸如查询集和模型实例复杂的数据转换为原生的Pyth ...
- CDR
伴随着新经济.独角兽一同被热议的,中国将很快推出存托凭证迎接独角兽回归.中国存托凭证(CDR)已成为当下热门话题.说不清CDR,还能和小伙伴们愉快地聊天吗? CDR到底是什么?它具有哪些优势?能否带来 ...
- [每日一题]面试官问:Async/Await 如何通过同步的方式实现异步?
关注「松宝写代码」,精选好文,每日一题 时间永远是自己的 每分每秒也都是为自己的将来铺垫和增值 作者:saucxs | songEagle 一.前言 2020.12.23 日刚立的 flag,每日一 ...
随机推荐
- Zabbix漏洞学习
Zabbix介绍 zabbix([`zæbiks])是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案. zabbix能监视各种网络参数,保证服务器系统的安全运营:并提供灵 ...
- .net core Docker 容器添加ffmpeg 获取视频信息和截图
最近在处理上传视频,需要获取视频信息和截图,这里就需要用到ffmpeg; 由于我的项目是在docker compose中运行调试,所以ffmpeg也需要在docker中能调用: 网上找到的方法在Doc ...
- Spring AOP学习笔记01:AOP概述
1. AOP概述 软件开发一直在寻求更加高效.更易维护甚至更易扩展的方式.为了提高开发效率,我们对开发使用的语言进行抽象,走过了从汇编时代到现在各种高级语言繁盛之时期:为了便于维护和扩展,我们对某些相 ...
- LeetCode 74,直击BAT经典面试题
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题43篇文章,我们今天来看一下LeetCode当中的74题,搜索二维矩阵,search 2D Matrix. 这题的 ...
- Java实现 LeetCode 826 安排工作以达到最大收益(暴力DP)
826. 安排工作以达到最大收益 有一些工作:difficulty[i] 表示第i个工作的难度,profit[i]表示第i个工作的收益. 现在我们有一些工人.worker[i]是第i个工人的能力,即该 ...
- Java实现 LeetCode 757 设置交集大小至少为2(排序+滑动窗口)
757. 设置交集大小至少为2 一个整数区间 [a, b] ( a < b ) 代表着从 a 到 b 的所有连续整数,包括 a 和 b. 给你一组整数区间intervals,请找到一个最小的集合 ...
- Java实现蓝桥杯模拟约数的个数
问题描述 1200000有多少个约数(只计算正约数). 答案提交 这是一道结果填空的题,你只需要算出结果后提交即可.本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分. pac ...
- Java实现 LeetCode 263 丑数
263. 丑数 编写一个程序判断给定的数是否为丑数. 丑数就是只包含质因数 2, 3, 5 的正整数. 示例 1: 输入: 6 输出: true 解释: 6 = 2 × 3 示例 2: 输入: 8 输 ...
- 用mvc框架查询数据库数据
介绍下mvc框架,mvc框架一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑. 首先我们 ...
- 07.Django-缓存
目录 缓存 一.如何提高网站并发量? 二.缓存方式 1. 开发调式缓存 2. 内存缓存 3. 文件缓存 4. 数据库缓存 5. Memcache缓存 5.1 使用python-memcached模块 ...