Python生成器详解 | 投稿
off999 2024-12-10 19:17 10 浏览 0 评论
程派微信号:codingpy
本文为作者、kissg.me博主赵喧典授权编程派原创发布。文章比较长,大家可以收藏了慢慢看哈~~
引文
编程派前几天推送了一篇文章,叫“Python学习进阶路线(简版)”,生成器(generator)
赫然在列.可是我不太会.不会怎么办?学咯。于是上网看了不少教程,又看了官方文档,学到了不少知识。在此,权且做个学习笔记,也与大家分享一下。
正文
要理解generator
,我们先从迭代(iteration)
与迭代器(iterator)
讲起.当然,本文的重点是generator
,iteration
与iterator
的知识将点到即止。直接看generator
迭代是重复反馈过程的活动,其目的通常是为了接近并到达所需的目标或结果。每一次对过程的重复被称为一次“迭代”,而每一次迭代得到的结果会被用来作为下一次迭代的初始值。
以上是维基百科对迭代的定义。在python中,迭代通常是通过for ... in ...
来完成的,而且只要是可迭代对象(iterable)
,都能进行迭代。这里简单讲下iterable
与iterator
:
iterable
是实现了__iter__()
方法的对象.更确切的说,是container.__iter__()
方法,该方法返回的是的一个iterator
对象,因此iterable
是你可以从其获得iterator
的对象.使用iterable
时,将一次性返回所有结果,都存放在内存中,并且这些值都能重复使用.以上说法严重错误!对于iterable
,我们该关注的是,它是一个能一次返回一个成员的对象(iterable is an object capable of returning its members one at a time),一些iterable
将所有值都存储在内存中,比如list
,而另一些并不是这样,比如我们下面将讲到的iterator
.
iterator
是实现了iterator.__iter__()
和iterator.__next__()
方法的对象.iterator.__iter__()
方法返回的是iterator
对象本身.根据官方的说法,正是这个方法,实现了for ... in ...
语句.而iterator.__next__()
是iterator
区别于iterable
的关键了,它允许我们显式地获取一个元素.当调用next()
方法时,实际上产生了2个操作:
更新
iterator
状态,令其指向后一项,以便下一次调用返回当前结果
如果你学过C++
,它其实跟指针的概念很像(如果你还学过链表的话,或许能更好地理解)。
正是__next__()
,使得iterator
能在每次被调用时,返回一个单一的值(有些教程里,称为一边循环,一边计算,我觉得这个说法不是太准确。但如果这样的说法有助于你的理解,我建议你就这样记),从而极大的节省了内存资源。另一点需要格外注意的是,iterator
是消耗型的,即每一个值被使用过后,就消失了。因此,你可以将以上的操作2理解成pop
。对iterator
进行遍历之后,其就变成了一个空的容器了,但不等于None
哦。因此,若要重复使用iterator
,利用list()
方法将其结果保存起来是一个不错的选择。
我们通过代码来感受一下。
既然提到了for ... in ...
语句,我们再来简单讲下其工作原理吧,或许能帮助理解以上所讲的内容。
>>> x = [1, 2, 3]
>>> for i in x:
...
...
我们对一个iterable
用for ... in ...
进行迭代时,实际是先通过调用iter()
方法得到一个iterator
,假设叫做X。然后循环地调用X的next()
方法取得每一次的值,直到iterator为空,返回的StopIteration
作为循环结束的标志。for ... in ...
会自动处理StopIteration
异常,从而避免了抛出异常而使程序中断。如图所示
磨刀不误砍柴工,有了前面的知识,我们再来理解generator
与yield
将会事半功倍。
首先先理清几个概念:
generator
: A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.
generator iterator
: An object created by a generator funcion.
generator expression
: An expression that returns an iterator.
以上的定义均来自python官方文档。可见,我们常说的生成器
,就是带有yield
的函数,而generator iterator
则是generator function
的返回值,即一个generator
对象,而形如(elem for elem in [1, 2, 3])
的表达式,称为generator expression
,实际使用与generator
无异。
其实说白了,generator
就是iterator
的一种,以更优雅的方式实现的iterator
。官方的说法是:
Python’s generators provide a convenient way to implement the iterator protocol.
你完全可以像使用iterator
一样使用generator
,当然除了定义。定义一个iterator
,你需要分别实现__iter__()
方法和__next__()
方法,但generator
只需要一个小小的yield
(好吧,generator expression
的使用比较简单,就不展开讲了。)
前文讲到iterator
通过__next__()
方法实现了每次调用,返回一个单一值的功能。而yield
就是实现generator
的__next__()
方法的关键!先来看一个最简单的例子:
第一次调用next()
方法时,函数似乎执行到yield 1
,就暂停了。然后再次调用next()
时,函数从yield 1
之后开始执行的,并再次暂停。第三次调用next()
,从第二次暂停的地方开始执行。第四次,抛出StopIteration
异常。
事实上,generator
确实在遇到yield
之后暂停了,确切点说,是先返回了yield
表达式的值,再暂停的。当再次调用next()
时,从先前暂停的地方开始执行,直到遇到下一个yield
。这与上文介绍的对iterator
调用next()
方法,执行原理一般无二。
有些教程里说generator
保存的是算法,而我觉得用中断服务子程序
来描述generator
或许能更好理解,这样你就能将yield
理解成一个中断服务子程序的断点
,没错,是中断服务子程序的断点。我们每次对一个generator
对象调用next()
时,函数内部代码执行到”断点”yield
,然后返回这一部分的结果,并保存上下文环境,”中断”返回。
怎么样,是不是瞬间就明白了yield
的用法?
我们再来看另一段代码。
我正是看到这个形式的generator
,懵了,才想要深入学习generator
与yield
的。结合以上的知识,我再告诉你,generator
其实有第2种调用方法(恢复执行),即通过send(value)
方法将value
作为yield
表达式的当前值,你可以用该值再对其他变量进行赋值,这一段代码就很好理解了。当我们调用send(value)
方法时,generator
正由于yield
的缘故被暂停了。此时,send(value)
方法传入的值作为yield
表达式的值,函数中又将该值赋给了变量s
,然后print函数打印s
,循环再遇到yield
,暂停返回。
调用send(value)
时要注意,要确保,generator
是在yield
处被暂停了,如此才能向yield
表达式传值,否则将会报错(如上所示),可通过next()
方法或send(None)
使generator
执行到yield
。
再来看一段yield
更复杂的用法,或许能加深你对generator
的next()
与send(value)
的理解。
上述代码既有yield value
的形式,又有value = yield
形式,看起来有点复杂。但以yield
分离代码进行解读,就不太难了。第一次调用next()
方法,执行到yield value
表达式,保存上下文环境暂停返回1
。第二次调用send(value)
方法,从value = yield
开始,打印,再次遇到yield value
暂停返回。后续的调用se nd(value)
或next()
都不外如是。
但是,这里就引出了另一个问题,yield
作为一个暂停恢复的点,代码从yield
处恢复,又在下一个yield
处暂停。可见,在一次next()
(非首次)或send(value)
调用过程中,实际上存在2个yield
,一个作为恢复点的yield
与一个作为暂停点的yield
。因此,也就有2个yield
表达式。send(value)
方法是将值传给恢复点yield
;调用next()
表达式的值时,其恢复点yield
的值总是为None
,而将暂停点的yield
表达式的值返回。为方便记忆,你可以将此处的恢复点记作当前的(current),而将暂停点记作下一次的(next),这样就与next()
方法匹配起来啦。
generator
还实现了另外两个方法throw(type[, value[, traceback]])
与close()
。前者用于抛出异常,后者用于关闭generator
.不过这2个方法似乎很少被直接用到,本文就不再多说了,有兴趣的同学请看这里。
小结
可迭代对象(Iterable)是实现了
__iter__()
方法的对象,通过调用iter()
方法可以获得一个迭代器(Iterator)。迭代器(Iterator)是实现了
__iter__()
和__next__()
的对象。for ... in ...
的迭代,实际是将可迭代对象转换成迭代器,再重复调用next()
方法实现的。生成器(generator)是一个特殊的迭代器,它的实现更简单优雅
yield
是生成器实现__next__()
方法的关键。它作为生成器执行的暂停恢复点,可以对yield
表达式进行赋值,也可以将yield
表达式的值返回。
相关推荐
- 独家 | 5 个Python高级特性让你在不知不觉中成为Python高手
-
你已经使用Python编程了一段时间,编写脚本并解决各种问题。是你的水平出色吗?你可能只是在不知不觉中利用了Python的高级特性。从闭包(closure)到上下文管理器(contextmana...
- Python装饰器
-
Python装饰器是一种用于修改函数或类的行为的特殊语法。它们允许在不修改原始代码的情况下,通过将函数或类作为参数传递给另一个函数来添加额外的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返...
- 中高阶Python常规用法--上下文管理器
-
Python以简单性和通用性著称,是一种深受全球开发人员喜爱的编程语言。它提供了大量的特性和功能,使编码成为一种愉快的体验。在这些功能中,一个经常被新手忽视的强大工具是上下文管理器。上下文管理器是高...
- Python小案例67- 装饰器
-
Python装饰器是一种用于修改函数或类的行为的特殊语法。它们允许在不修改原始代码的情况下,通过将函数或类作为参数传递给另一个函数来添加额外的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返...
- python常用的语法糖
-
概念Python的语法糖(SyntacticSugar)是指那些让代码更简洁、更易读的语法特性,它们本质上并不会增加新功能,但能让开发者更高效地编写代码。推导式写法推导式是Python最经典的...
- python - 常用的装饰器 decorator 有哪些?
-
python编程中使用装饰器(decorator)工具,可以使代码更简洁清晰,提高代码的重用性,还可以为代码维护提供方便。对于python初学者来说,根据装饰器(decorator)的字面意思并不...
- python数据缓存怎么搞 ?推荐一个三方包供你参考,非常简单好用。
-
1.数据缓存说明数据缓存可以说也是项目开发中比不可少的一个工具,像我们测试的系统中,你都会见到像Redis一样的数据缓存库。使用缓存数据库的好处不言而喻,那就是效率高,简单数据直接放在缓存中...
- 用于时间序列数据的Graphite监视工具
-
结合第三方工具,Graphite为IT性能监控提供了许多好处。本文介绍其核心组件,包括Carbon、Whisper以及安装的基本准则。Graphite监视工具可实时或按需,大规模地绘制来自多个来源的时...
- Python3+pygame实现的坦克大战
-
一、显示效果二、代码1.说明几乎所有pygame游戏,基本都遵循一定的开发流程,大体如下:初始化pygame创建窗口while循环检测以及处理事件(鼠标点击、按键等)更新UI界面2.代码创建一个m...
- Python之鸭子类型:一次搞懂with与上下文装饰器
-
引言在鸭子类型的理念的基础之上,从关注类型,转变到关注特性和行为。结合Python中的魔法函数的体系,我们可以将自定义的类型,像内置类型一样被使用。今天这篇文章中,接着该话题,继续聊一下with语法块...
- Python必会的50个代码操作
-
学习Python时,掌握一些常用的程序操作非常重要。以下是50个Python必会的程序操作,主要包括基础语法、数据结构、函数和文件操作等。1.HelloWorldprint("Hello,...
- 一文掌握Python 中的同步和异步
-
同步代码(Sync)同步就像在一个流水线上工作,每个任务都等待前一个任务完成。示例:机器A切割钢板→完成后,机器B钻孔→完成后,机器C上色。在Python中,同步代码看起来像这样:im...
- python 标注模块timeit: 测试函数的运行时间
-
在Python中,可以使用内置的timeit模块来测试函数的运行时间。timeit模块提供了一个简单的接口来测量小段代码的执行时间。以下是使用timeit测试函数运行时间的一般步骤:导入...
- Python带你找回童年的万花尺
-
还记得小时候的万花尺吧?这么画:一点也不费脑筋,就可以出来这么多丰富多彩的复杂几何图形。具体而言,可以用万花尺玩具(如图2-1所示)来绘制数学曲线。这种玩具由两个不同尺寸的塑料齿轮组成,一大一小。小的...
- Python 时间模块深度解析:从基础到高级的全面指南
-
直接上干货一、时间模块核心类介绍序号类名说明1datetime.datetime表示一个具体的日期和时间,结合了日期和时间的信息。2datetime.date表示一个具体的日期。3datetime.t...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- python计时 (54)
- python安装路径 (54)
- python类型转换 (75)
- python进度条 (54)
- python的for循环 (56)
- python串口编程 (60)
- python写入txt (51)
- python读取文件夹下所有文件 (59)
- java调用python脚本 (56)
- python操作mysql数据库 (66)
- python字典增加键值对 (53)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python qt (52)
- python人脸识别 (54)
- python斐波那契数列 (51)
- python多态 (60)
- python命令行参数 (53)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- centos7安装python (53)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)