最近在学习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. 使用Burpsuite对手机抓包的配置

    之前使用dSploit的时候就一直在想怎么对手机进行抓包分析,前两天使用了Burpsuite神器,发现通过简单的配置就可以抓手机app的数据包了,进而分析手机app的流量. 配置环境: 1.win7下 ...

  2. 拨号vps,拨号vps是什么意思干什么用的,如何使用拨号vps

    首先,拨号vps是动态IP的VPS.vps虚拟服务器.拨号服务器.有些业务,如刷单.投票等操作对ip地址有限制,不能过多的使用.而拨号VPS通过拨号上网,每拨号一次号,就变一次IP,完成ip地址的动态 ...

  3. Chisel3 - bind - Data

    https://mp.weixin.qq.com/s/ENJVkz88sGgyODRNCu9jhQ   介绍Data类中的binding的定义和用法.   Binding stores informa ...

  4. Chisel3 - Tutorial - Tbl

    https://mp.weixin.qq.com/s/e8vJ8claauBtiuedxYYaJw   实现可以动态索引的表.   参考链接: https://github.com/ucb-bar/c ...

  5. (String),toString(),String.valueOf()

    String.valueOf("")的源码:(推荐这种写法) 注意:obj问null时,返回值是字符串"null" toString("") ...

  6. 【Linux】文件权限,ssh免密登录

    1.文件/文件夹权限 例子: -rw-r--r--. 1 root root 12288 Aug 21 09:50 aliases.db drwxr-xr-x. 2 root root 4096 Au ...

  7. Java实现图形化计算器

    package java计算器; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionL ...

  8. Java实现选择问题

    选择问题是求一个n个数列表的第k个最小元素的问题. 那么如何寻找n个元素中第k个最小元素呢? package com.liuzhen.chapter4; public class SelectProb ...

  9. Java实现第九届蓝桥杯耐摔指数

    耐摔指数 题目描述 x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机. 各大厂商也就纷纷推出各种耐摔型手机.x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后 ...

  10. CSS清除浮动&内容居中&文字溢出

    学习! 1.CSS清除浮动的方法 (1)添加标签清除浮动: 在浮动元素结尾处,并列的添加标签<div style="clear:both;"></div>. ...