Python核心编程之生成器
生成器
1. 什么是生成器
大家知道通过列表生成式(不知道的可自行百度一下),我们可以直接创建一个列表,但是,受内存限制,列表内容肯定是有限的。比如我们要创建一个包含100万个元素的列表,这100万个元素会占用很大的内存空间,而且如果我们仅仅需要访问前面几个元素的话,那后面绝大多数的元素占用的空间就都白白浪费了。设想一下如果列表中的元素能够在循环使用的过程中推算出来,即用一个推算一个,这样是不是就不用一次性生成全部元素从而大大节省了内存空间呢。在python中这种一边循环一边计算的机制称为生成器:generator。
2. 创建生成器的方法
这里主要介绍2中创建生成器的方法:
方法一:这种方法最简单,只要把一个列表生成式的[]改成()即可,如下:
>>>L = [x*2 for x in range(10)] >>>L >>>[0,2,4,6,8,10,12,14,16,18] >>>G = (x*2 for x in range(10)) >>>G >>><generator object <genexpr> at 0x7f628d120kb0
创建L和G的区别仅在于最外层的[]和(),但结果是L是一个列表而G是一个生成器,我们知道列表我们可以直接打印出来,那么生成器的值我们应该怎么获取呢,同样有2中方式,第一是通过next()函数获得生成器的下一个返回值:
>>>next(G) >>>0 >>>next(G) >>>2
但是这样获取太过麻烦,如果像上面说的需要100万个元素时我们这样写岂不是会累死,而且如果到最后没有元素还会产生StopIteration的异常。
下面说一下获取生成器值的第二种方法:使用for循环,因为生成器也是可迭代的对象,我们创建一个生成器后基本是不会使用next函数调用的,而是通过for循环来迭代,并且不用关心StopIteration异常。
方法二:使用yield关键字创建生成器
方法一种我们介绍了最简单的生成方法,是把列表生成式的[]改为(),这种方式适合推算算法比较简单的场景。试想如果推算算法和复杂,用类似列表生成式一行代码无法实现的时候该怎么办呢?幸运的是generator非常强大,可以把一个函数作为一个生成器。比如著名的斐波那契数列,除第一和第二个数一样外,其它任意一个数都是由前两个数相加而得到:1,1,2,3,5,8...
这时列表生成式就无法满足了,但是我们可以用函数很容易的打印出来:
def fib(times):
n = 0
a,b = 0,1
while n < times:
print(b)
a,b = b,a+b
n+=1
return 'done'
#调用fib函数生成5个数
fib(5)
#输出结果如下:
1
1
2
3
5
done
可以看出,fib函数定义了斐波那契数列的推算规则,可以从第一个元素开始推算出后续任意的元素,这种逻辑非常类似生成器generator,也就是说上面的函数离生成器仅一步之遥。这里只需要把print(b)改为yield b就变成一个生成器了。
def fib(times):
n = 0
a,b = 0,1
while n < times:
yield b
a,b = b,a+b
n+=1
return 'done' for x in fib(5):
print(x) #输出结果如下:
1
1
2
3
5
但是这里有个问题就是用for循环调用generator时就无法获取到生成器中的return返回值了,如果想要获取返回值,还得借助next函数并捕获StopIteration异常,返回值则包含在StopIteration的value中,如下:
g = fib(5)
while True:
try:
x = next(g)
print(x)
except StopIteration as e:
print("生成器的return返回值:%s" % e.value)
break #输出结果:
1
1
2
3
5
生成器的return返回值:done
3. yield关键字
接下来说一下yield关键字,这里暂时可以把yield理解为return,然后我们用一个列子来说明yield与return的区别。
def fun():
print("====start=====")
while True:
res = yield 8
print('res: %s' % res) g = fun()
print(next(g))
print('*'*20)
print(next(g)) #输出结果如下
====start=====
8
********************
None
8
代码解读:首先创建一个生成器。接下来当程序运行到g = fun()时,实际上并不会去执行fun里面的代码,而是相当于创建了一个生成器对象并赋值给g。程序继续往下执行,当运行到第一个print(next(g))时,因为遇到了next函数,fun中的代码开始执行,首先打印输出====start=====,然后进入while循环执行yield 8,前面说过yield有return的功能,这时程序将数字8return出去,然后程序停止,fun中yield后面的代码不会被执行(注意这里执行完yield 8之后,fun就已经停止往后运行,并不会执行赋值操作,也就是说没有将8赋值给res),所以这里我们看到的输出结果是====start=====和数字8
第二步程序继续执行print(‘*’*20)然后输出20个*
第三步当程序遇到第二个next函数时,跟上面那个next差不多,但不同的是,这个时候程序将从上一个next停止的地方开始执行,也就是说程序又跳回到res = yield 8这一行并且开始执行赋值操作,然而在刚刚执行第一个next的时候=右边的值已经被return出去了并没有执行赋值操作,所以这个时候右边并没有值,因此这个时候res被赋值为None,所以我们看到程序输出为None
第四步程序继续执行while循环,又遇到yield关键字然后同样将8return出去程序停止,所以None后面又输出一个8
这就是yield被return的区别,带yield函数就是一个生成器,生成器有个next函数,当第一次调用next函数时,程序将从生成器fun的开始执行,当继续调用next 时,这一次的next 将从上一次的next停止的地方开始执行,也就是从yield关键字的地方开始执行,而并不是每次next都从生成器的开始执行,然后遇到yield后把要生成的值return出去程序停止(这里的程序停止指的是生成器中yield后面的代码不会执行,而不是整个程序停止)。
那么如果我们想要接收res的值应该怎么办呢?接下来介绍生成器的另外一个函数send函数。
4. send函数
我们将上面的代码修改一下
def fun():
print("====start=====")
while True:
res = yield 8
print('res: %s' % res) g = fun()
print(next(g))
print('*'*20)
print(g.send(5)) #输出结果如下
====start=====
8
********************
5
8
看一下这里只是将原来第一个print(next(g))改为print(g.send(5)),那么res输出结果由原来的None变成了5。这是为什么呐,原来在调用send函数时会将所传递的参数发送给生成器并赋值给res,上面说过return的时候并没有把8赋值给res,下次执行的时候由于值已经被return出去所以只好将None赋值给res。而如果用send的话,开始执行的时候与next一样,也是先从上一次停止的地方开始执行,不同的是send会先把发送过去的参数5赋值给res,然后再继续往后执行,遇见下一回的yield,return出结果后结束。
总结
生成器是这样一个函数,它记住上一次返回时在函数体中的位置,对生成器函数的第二次或第n次调用跳转至该函数上次停止的地方,而上次调用的所有局部变量都保持不变。
生成器的特点:
1. 节约内存
2. 迭代到下一次调用时,所使用的参数都是第一次所保留下的,也就是说在整个函数调用的所有的参数都是第一次调用所保留的而不是新创建的。
Python核心编程之生成器的更多相关文章
- Python核心编程的四大神兽:迭代器、生成器、闭包以及装饰器
生成器 生成器是生成一个值的特殊函数,它具有这样的特点:第一次执行该函数时,先从头按顺序执行,在碰到yield关键字时该函数会暂停执行该函数后续的代码,并且返回一个值:在下一次调用该函数执行时,程 ...
- python核心编程第二版笔记
python核心编程第二版笔记由网友提供:open168 python核心编程--笔记(很详细,建议收藏) 解释器options:1.1 –d 提供调试输出1.2 –O 生成优化的字节码(生成 ...
- 学习《Python核心编程》做一下知识点提要,方便复习(一)
学习<Python核心编程>做一下知识点提要,方便复习. 计算机语言的本质是什么? a-z.A-Z.符号.数字等等组合成符合语法的字符串.供编译器.解释器翻译. 字母组合后产生各种变化拿p ...
- python核心编程--笔记
python核心编程--笔记 的解释器options: 1.1 –d 提供调试输出 1.2 –O 生成优化的字节码(生成.pyo文件) 1.3 –S 不导入site模块以在启动时查找pyt ...
- Python核心编程第二版(中文).pdf 目录整理
python核心编程目录 Chapter1:欢迎来到python世界!-页码:7 1.1什么是python 1.2起源 :罗萨姆1989底创建python 1.3特点 1.3.1高级 1.3.2面向 ...
- python核心编程--笔记(不定时跟新)(转)
的解释器options: 1.1 –d 提供调试输出 1.2 –O 生成优化的字节码(生成.pyo文件) 1.3 –S 不导入site模块以在启动时查找python路径 1.4 –v ...
- python核心编程笔记(转)
解释器options: 1.1 –d 提供调试输出 1.2 –O 生成优化的字节码(生成.pyo文件) 1.3 –S 不导入site模块以在启动时查找python路径 1.4 –v 冗 ...
- Python核心编程(第二版)PDF
Python核心编程(第二版) 目录 第1部分 Python核心第1章 欢迎来到Python世界1.1 什么是Python1.2 起源1.3 特点1.3.1 高级1.3.2 面向对象1.3.3 可升级 ...
- 拒绝从入门到放弃_《Python 核心编程 (第二版)》必读目录
目录 目录 关于这本书 必看知识点 最后 关于这本书 <Python 核心编程 (第二版)>是一本 Python 编程的入门书,分为 Python 核心(其实并不核心,应该叫基础) 和 高 ...
随机推荐
- JVM-STW-stop the world
Stop The World Stop一the一World,简称STW,指的是Gc事件发生过程中,会产生应用程序的停顿.停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称 ...
- ES6 常用总结——第二章(字符串的扩展)
ES6为字符串添加了遍历器接口,使得字符串可以被for...of循环遍历. for (let codePoint of 'foo') { console.log(codePoint)} // &quo ...
- 看动画学算法之:排序-count排序
目录 简介 count排序的例子 count排序的java实现 count排序的第二种方法 count排序的时间复杂度 简介 今天我们介绍一种不需要作比较就能排序的算法:count排序. count排 ...
- 龙芯3A4000-Debian 10上常用软件记录
所用平台 硬件:龙芯3a4000 (MIPS64el) 操作系统:Debian 10(buster)+ MATE桌面 内核版本:4.19.90 以下所有软件同样适用于x86体系结构的Linux发行版. ...
- Spring的IOC控制反转和依赖注入-重点-spring核心之一
IoC:Inverse of Control(控制反转): 读作"反转控制",更好理解,不是什么技术,而是一种设计思想,好比于MVC.就是将原本在程序中手动创建对象的控制权,交由S ...
- spring cloud微服务快速教程之(十四)spring cloud feign使用okhttp3--以及feign调用参数丢失的说明
0-前言 spring cloud feign 默认使用httpclient,需要okhttp3的可以进行切换 当然,其实两者性能目前差别不大,差别较大的是很早之前的版本,所以,喜欢哪个自己选择: 1 ...
- redis 的基本使用
Linux上查看redis是否运行 ps -ef | grep redis 还能查看到Linux的安装路径. set rediskey redisvalue 在redis里面存储了一个名称为r ...
- XDCMS审计(复现)
最近开始学习审计,拿一些简单cms的漏洞复现一下.源码在文末会分享出来. 0x01 index.php <?php if(!file_exists("data/config.inc.p ...
- 解决 webpack .\src\main.js .\dist\bundle.js 错误
打包的命令格式:webpack 要打包的文件的路径 打包好的输出文件的路径 栗子: webpack .\src\main.js .\dist\bundle.js 提示错误,错误信息如下: 错误原因 w ...
- Python+selenium+unittest实现网址登陆及页面跳转校验
这个网址登陆的单元测试代码都写在一个文件中,方便初学者查看和调试:实际测试工作中根据情况进行模块化处理. 话不多说,分布上代码 一.import必要模块 二.创建测试类,初始化测试环境 三.定义登陆方 ...