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 核心(其实并不核心,应该叫基础) 和 高 ...
随机推荐
- python测试http、websocket接口
测试环境有个项目需要每天构造数据,来尽量保证测试环境和生产环境数据量保持一致.需要生成订单后商家接单完成,以下是代码,主要是用接口完成 创建订单 # coding=utf-8 import reque ...
- Mac部署spark2.4.4
环境信息 操作系统:macOS Mojave 10.14.6 JDK:1.8.0_211 (安装位置:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jd ...
- springboot:This application has no explicit mapping for /erro
springboot启动没有报错,但是访问的时候返回如上图的错误.看报错内容感觉是没有这个mapping对应的接口.但是确实写了. 最终发现是因为springboot的启动类放的位置不对.启动类所在的 ...
- 【FastDFS】小伙伴们说在CentOS 8服务器上搭建FastDFS环境总报错?
写在前面 在[冰河技术]微信公众号的[分布式存储]专题中,我们分别搭建了单节点FastDFS环境和高可用FastDFS集群环境.但是,之前的环境都是基于CentOS 6.8服务器进行搭建的.很多小伙伴 ...
- mysql-python for mac安装过程
转载:https://yiweifen.com/v-1-338191.html
- 渗透测试之信息收集(Web安全攻防渗透测试实战指南第1章)
收集域名信息 获得对象域名之后,需要收集域名的注册信息,包括该域名的DNS服务器信息和注册人的联系方式等. whois查询 对于中小型站点而言,域名所有人往往就是管理员,因此得到注册人的姓名和邮箱信息 ...
- 花时三月 终于Spring Boot 微信点餐开源系统! 附源码
架构 前后端分离: Nginx与Tomcat的关系在这篇文章,几分钟可以快速了解: https://www.jianshu.com/p/22dcb7ef9172 补充: set ...
- burp suite 之 Scanner(漏洞扫描)
Scanner选项:是一个进行自动发现 web 应用程序的安全漏洞的工具. 将抓取的包 通过选项卡发送至 Scanner下的Scan queue 首先来介绍 Scanner 下的 lssue acti ...
- 升级到win8.1导致oracle服务丢失的处理
针对升级到win8.1导致oracle服务丢失的处理 1.首先保证oracle相关程序能够运行,如net manager,如果能够运行,说明oracle安装仍然有效,只是因为服务被"净化&q ...
- Centos-进程运行状态-ps
ps 显示系统进程在瞬间的运行状态 相关选项 -a 显示所有用户的进程,包含每个程序的完整路径 -x 显示所有系统程序,包括那些没有终端的程序 -u 显示使用者的名称和起始时间 -f 详细显示程序执 ...