python语言的产生_Python生成器是什么(超级详细)

之前我们讨论了高效的推导式。通过推导式,我们可以直接创建一个列表、字典或集合。但是,由于受到内存的限制,这些可迭代对象(列表、字典或集合)的容量是有限的。

比如,创建一个包含 10 万个元素的列表,不仅要占用很大的存储空间,而且根据局部性原理,在一段时间内我们要访问的仅仅局限于相邻的若干个元素,即使把所有元素都加载到内存之中,它们被“临幸”的概率也非常小。因此,大部分的存储空间其实是被白白浪费了。

基于此,我们就会有这样的需求:这些元素能不能按照某种算法推算出来,然后在后续循环过程中,根据这些元素不断推算出其他被访问的元素呢?这样一来,就不必创建完整的列表、字典或集合了,从而节省了大量的空间。在 Python 语言中,这种一边循环一边计算的机制,称为生成器。

Python生成器的定义

创建一个生成器并不复杂,方法也有很多。最简单的一种方法莫过于把一个列表推导式最外层的标记方括号[ ]改成圆括号( ),这样一个生成器就创建好了,示例代码如下。

In [1]: n = 10

In [2]: a = [x**2 for x in range (n) if x%2 == 0] #这是一个列表推导式

In [3]: print(a) #可正常输出

[0, 4, 16, 36, 64]

In [4]: type(a) #验明正身

Out[4]: list

In [5]: b = (x**2 for x in range(n) if x % 2==0) #这是一个生成器

In [6]: print(b) #无法直接输出

at 0xl07c88d00〉

In [7]: type(b) #验明正身

Out[7]: generator

上述代码的 In [2] 处是一个标准的列表生成式,一旦执行,就会把符合条件的列表元素全部加载到内存之中,此处生成的元素个数仅为 1 0个。但如果 n 为 100 万呢?列表 a 就会生成同样数量级别的元素,这无疑会浪费大量内存。

而在输入 In [5] 处,我们将 In [2] 处的最外层方括号[ ]替换为圆括号( ),这时它的类型就截然不同了。从 In [4] 和 In [7] 处输出的对象类型可以看出,前者 a 是一个列表,而后者 b 则是一个生成器。

在本质上,生成器就是一个生成元素的函数。现在你应该明白 In [5] 处最外层的那对圆括号( )的意义了吧,它不是“元组”生成式的标志,而更像是某个函数的标志(函数最核心的标志之一就是那对括号)。我们把这种表达式叫作生成器表达式(generator expression)

列表中的元素可以直接利用 print( ) 语句输出(如上述代码 In [6] 处),但同样的办法对生成器而言却是不可行的,解释器仅能给出生成器的地址信息。那么,该如何输出生成器中的每一个元素呢?这时,就需要借助全局内置函数 next( ),获得生成器的下一个返回值。

next( ) 函数好像拥有记忆一般,每使用一次 next( ) 函数就会顺序输出生成器的下一个元素,而不是从最开始的位置输出,直到输出最后一个元素,没有元素可输出时,就会抛出 StopIteration 异常。

In [8]: next(b)

Out[8]: 0

In [9]: next(b)

Out[9]: 4

In [10]: next(b)

Out[10]: 16

In [11]: b.__next__() #或用对象a的内部函数 __next__()来访问下一个元素

Out[11]: 36

...

由于生成器也是一个特殊的迭代器,所以它也会有内置函数 __next__(),在输入 In [11] 处,我们调用了它的内置函数 __next__(),也实现了和全局函数 next( ) 相同的效果。当我们不断执行 next(a) 时,它会不断输出 a 的下一个元素,直到没有更多的元素输出时,它会抛出 StopIteration 异常。

通常,生成器的正确打开方式并不是“傻乎乎”地反复调用 next( ) 函数,而是和循环(如 for、while 等)配套使用,由于 Python 语法糖会为我们保驾护航,确保访问不会越界,因此不会发生 StopIteration 异常,代码如下所示:

In [12]: a = (x**2 for x in range (n) if x%2 == 0) #此处 n = 10

In [13]: for num in a:

...: print(num)

...:

0

4

16

36

64

Python利用yield创建生成器

生成器的功能很强大。如果推算的算法比较复杂,难以利用列表推导式来生成,这时就可以使用含有 yield 关键字的函数。下面举例说明。

例如,在著名的斐波那契数列(Fibonacci)中,除第一个数和第二个数都为 1 之外,任意后面一个数都可由前两个数相加得到。

1,1, 2, 3, 5, 8, 13, 21, 34,...

分别以斐波那契数列中的元素为半径画出 1/4 圆,这些 1/4 圆连接起来的曲线称为斐波那契螺旋线,也称“黄金螺旋”,如图 1 所示。很神奇的是,在自然界中,很多生物(如向日葵、仙人掌、海螺等)中都存在斐波那契螺旋线的图案。

3-200512145F2561.gif

图 1:斐波那契螺旋线

回到关于生成器的讨论上来。生成斐波那契数列的过程相对比较复杂,难以利用列表推导式简练地表达出来,但可以用一个多行的函数描述出来,参见例 1。

【例 1】生成斐波那契数列的函数(fibonacci.py)

def fibonacci(xterms):

n, a, b = 0, 0, 1 #变量初始化

while n < xterms:

print(b, end = ' ')

a, b = b, a + b #变量更新

n = n + 1

return '输出完毕'

fibonacci(10)

程序执行结果为:

1 1 2 3 5 8 13 21 34 55

第 02 行和第 05 行代码体现了 Python 的特色—多变量赋值。

第 02 行代码的功能是对三个变量进行初始化赋值,它等价于如下代码:

n = 0

a = 0

b = 1

第 05 行代码的功能是循环更新变量值,它等价于如下代码:

a = b

b = a + b

由上面的代码可以看出,使用多变量赋值可以大大简化代码。但实际上,如前讨论,第 02 行和第 05 行实现的就是两个匿名元组之间的赋值。print( ) 默认的输出终结符是换行符,这里为了不占用打印空间,改成了空格(通过设置print()函数中的参数 end = ' '来实现),因此所有元素输出以空格隔开。

仔细观察可以看出,实际上,fibonacci( ) 函数中第 05 行代码已经清楚地定义了斐波那契数列的推算规则,我们可以从第一个元素开始,推算出后续任意元素。而这种推导逻辑已经非常接近生成器。也就是说,把上述函数稍加改造,就能把fibonacci()函数变成生成器:只需要把向屏幕输出的 print(b) 改为专用的 yield b 就大功告成了。参见例 2。

【例 2】生成斐波那契数列的生成器(fibonacci-gen.py)

def fibonacci(xterms):

n, a, b = 0, 0, 1

while n < xterms:

yield b #表明这是一个生成器

a, b = b, a + b

n = n + 1

return '输出完毕'

例 1 与例 2 的核心区别在于第 04 行,例 2 的第 04 行使用了关键字“yield”,这个关键字的本意就是“生产、产出”,如果某个函数定义中包含 yield 关键字,那么这个函数就不一般了,它不再是一个普通函数,而是一个生成器。

将上述函数加载到内存中以后,我们可以用如下代码来进行测试:

In [1]: func = fibonacci (10)

In [2] : func #并不直接输出

Out[2]:

通过前面的讨论,我们知道,In [1] 处的代码并不会执行 fibonacci( ) 函数,而是返回一个可迭代对象!这个对象并不能直接输出(见 In [2] 处),那该如何正确输出我们想要的结果呢?

第一种方法就是前面提到的反复利用 next( ) 函数,代码如下:

In [3]: next(func)

Out[3]: 1

In [4]: next(func)

Out[4]: 1

In [5]: next(func)

Out[5]: 2

In [6]: next(func)

Out[6]: 3

...

通过 next( ) 不断返回数列的下一个数,内存占用始终为常数。这是与列表推导式的显著不同。显然,如果生成器中“蕴涵”的数据较大,每次手动输入一个 next(func),才输出一个数据,麻烦至极。

因此,第二种方法更为常见,那就是和循环结构配套使用。我们重新加载 In [1] 处的代码并再次运行如下代码:

ln [7]: for item in func:

print(item, end = ' ')

程序执行结果为:

1 1 2 3 5 8 13 21 34 55

前面的几个生成器的案例其实并不实用,生成器的最佳应用场景在于:我们不想将所有计算出来的大量结果一块保存到内存之中。因为这样做会浪费大量不必要的内存资源。例如,将上面代码 In[1] 处的 10 改成 1000000,这时生成器的优势就体现出来了。因为生成器会“临时抱佛脚”,需要谁,就按照规则“临时”生成谁,它就好比是一个“经济适用房”,占用空间不大,但能解决实际问题。

Python生成器的执行流程

在这里,需要特别注意的是,生成器和函数的执行流程不一样。普通函数遇到 return 语句或者执行到最后一行函数语句时就会返回,结束整个函数的运行。

而变成生成器的函数,在每次调用 next( ) 的时候执行,遇到 yield 语句就“半途而废”,再次执行时,就会从上次返回的 yield 语句处接着往下执行。

下面列举一个简单的例子说明生成器的执行流程,见例 3。

【例 3】生成器的执行流程(my_gen.py)

def my_gen():

print('我是第1次返回')

yield(1)

print ('我是第2次返回')

yield(2)

print('我是第3次返回')

yield(3)

由于上述函数中含有 yield 语句,很显然,这是一个生成器。将上述函数加载到内存中之后, 我们来调用这个生成器。在调用生成器之前,首先要生成一个生成器对象,然后用 next( ) 函数不断获得下一个返回值,在 IPython 中的验证代码如下:

In [1]: gen = my_gen() #创建生成器对象

In [2]: next(gen) #输出生成器第一个元素,即第一个yield语句运行结果,并返回

我是第1次返回

Out[2]: 1

In [3]: next(gen) #从例 1 的第 04 行开始执行

我是第2次返回

Out [3] : 2

In [4]: next(gen) #从例 1 的第06行开始执行

我是第3次返回

Out[4]: 3

In [5]: next(gen) #无匹配的yield语句运行结果,发生异常,报错!

-----------------------------------------------------------------------

Stopiteration Traceback (most recent call last)

in

-----> 1 next(gen)

Stopiteration:

总结一下,在本质上,生成器就是一种元素生成函数,它和普通函数的不同之处在于,它的返回值不是通过 return 返回的,而是通过 yield 返回的。

另外一个需要注意的地方是,含有 yield 语句的函数中如果还配有return语句,那么这个 return 语句并不是用于函数正常返回的,而是 StopIteration 的异常说明。也就是说,生成器没有办法使用 return 的返回值。如果想获得该返回值,需要捕获 StopIteration 异常,然后输出 StopIteration.value。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/489237.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

俄罗斯“木船”机器人系统将于2020年部署部队

来源&#xff1a;国防科技要闻据悉&#xff0c;俄罗斯地面部队已完成“木船”&#xff08;Kungas&#xff09;机器人系统样机的国家试验&#xff0c;将于2020年开始部署部队&#xff0c;用于作战试验。发展背景自2010年以来&#xff0c;俄罗斯机器人系统进入快速发展时期。2014…

线程停止继续_晓龙吊打面试官系列: 如何优雅的停止一个线程

一、什么时候我们需要中断一个线程在实际的开发中&#xff0c;有很多场景需要我们中断一个正在运行的线程&#xff0c;就比如&#xff1a;当我们使用抢票软件时&#xff0c;其中某一个通道已经抢到了火车票&#xff0c;这个时候我们就需要通知其他线程停止工作。当我们希望在一…

hive读取hdfs存放文件_数据获取层之Flume快速入门(一) 实时监控单个追加文件

实时监控 Hive 日志&#xff0c;并上传到 HDFS 中实现步骤1、Flume 要想将数据输出到 HDFS&#xff0c;必须持有 Hadoop 相关 jar 包commons-configuration-1.6.jar、hadoop-auth-2.7.2.jar、hadoop-common-2.7.2.jar、hadoop-hdfs-2.7.2.jar、commons-io-2.4.jar、htrace-core…

2019-2020中国趋势报告,203页PPT解读16大机会

来源&#xff1a;企鹅智库 报告如下未来智能实验室是人工智能学家与科学院相关机构联合成立的人工智能&#xff0c;互联网和脑科学交叉研究机构。未来智能实验室的主要工作包括&#xff1a;建立AI智能系统智商评测体系&#xff0c;开展世界人工智能智商评测&#xff1b;开展互联…

行程单图片python预处理_GCC编译过程(预处理-gt;编译-gt;汇编-gt;链接)

前言如果你使用集成环境开发。那么你点击编译按钮就可生成可执行文件。但是C程序从源代码到二进制行程序都经历了那些过程&#xff1f;你知道吗&#xff1f;这些过程集成开发环境在点击编译按钮后都做完了&#xff0c;如果编译没有出错&#xff0c;即可生成可执行文件。本文将以…

sqlite查询乘以某列如果是null就换成_大数据之Hive group by with cube/rollup分组查询...

group bysql 查询时&#xff0c;我们常将聚合函数和group by 结合起来对某一个或多个字段进行分组查询&#xff0c;例如&#xff1a;select addcode,count(distinct sbtid)uv from tb_hive_window group by addcode;---------------| addcode | uv |---------------| 0002 …

可以操作excel吗_Excel快速填充,这四种方法你会吗?操作逆天告别加班

在Excel的表格制作中&#xff0c;仅仅会复制粘贴可是不够的&#xff0c;还需要掌握更多的技能&#xff0c;来提升我们的工作效率&#xff01;我们在进行Excel报表制作的时候&#xff0c;如果要批量填充序号&#xff0c;有多少种方法呢&#xff1f;下面给大家简单介绍一下这四种…

2G---5G与未来天线技术

本文来源&#xff1a;滤波器过去二十年&#xff0c;我们见证了移动通信从1G到4G LTE的转变。在这期间&#xff0c;通信的关键技术在发生变化&#xff0c;处理的信息量成倍增长。而天线&#xff0c;是实现这一跨越式提升不可或缺的组件。按照业界的定义&#xff0c;天线是一种变…

python操作csv文件第7行开始的数据_Python教程-Python读写CSV文件

前言 本教程学习在Python中使用CSV文件。CSV&#xff08;逗号分隔值&#xff09;格式是在电子表格和数据库中使用的非常流行的导入和导出格式。Python语言包含该模块&#xff0c;该模块具有用于读取和写入CSV格式的数据的类。csv 使用csv.reader&#xff08;&#xff09;读取CS…

基于STM32的高精度频率计设计

前言 本文记录了博主完成的一个课设作品&#xff08;学分为3.5分&#xff09;&#xff0c;题目需要利用ARM做出一个高精度频率计。具体要求如下&#xff1a; 1&#xff09;实现对10M以内数字信号频率的高精度测量&#xff0c;频率测量误差不大于0.01%&#xff1b; 2&#xff0…

数学的意义(一)

来源&#xff1a; 数学职业家数学既是一种文化、一种“思想的体操”&#xff0c;更是现代理性文化的核心。马克思说&#xff1a;“一门科学只有当它达到了能够成功地运用数学时&#xff0c;才算真正发展了。”在前几次科技革命中&#xff0c;数学大都起到先导和支柱作用。我们不…

python弹球小游戏程序_Python实现弹球小游戏

本文主要给大家分享一个实战项目&#xff0c;通过python代码写一款我们儿时大多数人玩过的游戏---小弹球游戏。只不过当时&#xff0c;我们是在游戏机上玩&#xff0c;现在我们通过运行代码来玩&#xff0c;看看大家是否有不一样的体验&#xff0c;是否可以重温当年的乐趣呢&am…

强化学习决策生成-以 Q-learning 为例

强化学习决策生成-以 Q-learning 为例

【数据中台】关于数据中台系统,需要了解哪些技术?

来源&#xff1a;产业智能官国家建材大数据研究中心今天让我们全面解读中台&#xff0c;包括企业为什么要平台化&#xff0c;目前中台都有哪些形式&#xff0c;实施中台系统的优势、面临的问题以及建议都有哪些&#xff1f;中台这个概念早期是由美军的作战体系演化而来的&#…

什么是启发式?什么是产生式?

来源&#xff1a;人机与认知实验室一般而言&#xff0c;机器常常被设定从已知推未知&#xff0c;而人们不时会从未知&#xff08;假设&#xff09;推未知&#xff0c;特殊情形下也有从未知推已知的&#xff0c;这些推导中常见的有产生式和启发式&#xff0c;那么究竟什么是产生…

Maven项目错误解决小结

http://blog.csdn.net/typa01_kk/article/details/49185759 Maven项目错误解决小结 注&#xff1a;整理错误&#xff0c;不喜欢为了一个小问题&#xff0c;占篇幅&#xff0c;所以请CtrlF自己查看&#xff0c;定位问题&#xff0c;愿为解决. 注&#xff1a;网络上的错误解决经验…

python交并补_python两个列表求交、并、差

在python中&#xff0c;如果有两个数组&#xff0c;分别要求交集&#xff0c;并集与差集&#xff0c;怎么实现比较方便呢&#xff1f; 当然最容易想到的是对两个数组做循环&#xff0c;即写两个for循环来实现。这种写法大部分同学应该都会&#xff0c;而且也没有太多的技术含量…

可怕!贺建奎论文手稿意外曝光!基因编辑实验充满谎言

来源&#xff1a;学研加中国双胞胎的基因编辑可能已经失败&#xff0c;并产生了意想不到的突变。这是科学家们阅读了原南方科技大学副教授贺建奎论文手稿后&#xff0c;得出的结论。贺建奎基因编辑婴儿事件2018年11月26日&#xff0c;一则《世界首例免疫艾滋病的基因编辑婴儿在…

l360废墨收集垫清零_知识分享003:EPSON L360打印机出现故障-废墨计数清零

故障现象&#xff1a;客户的EPSON L360打印机出现故障&#xff0c;纸张和墨水报警灯交替闪烁。无法打印原因分析&#xff1a;确定纸张和墨水的是处于正常状态&#xff0c;上网查了&#xff0c;可能是废墨收集到达了上限。需要做清零处理。解决方法&#xff1a;根据这个型号EPSO…

R语言-处理异常值或报错的三个示例

R语言-处理异常值或报错的三个示例 之前用rvest帮人写了一个定期抓取amazon价格库存&#xff0c;并与之前价格比较的小程序&#xff0c;算是近期写过的第一个完整的程序了。里面涉及了一些报错的处理。 这里主要参考了stackoverflow上的以下问答&#xff1a; How to skip an er…