首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

python-可迭代对象、迭代器、生成器、序列

这几个玩意比较绕,虽然平时用起来一般没什么问题,但看一些文档的时候,什么iterable, iterator一不留神就傻傻分不清楚了

注: 下文代码里会使用两个内置模块

基本概念

iterable: 可迭代对象,简单来说就是可以运行 而不报错的对象

iterator: 迭代器,是用来对可迭代对象进行迭代的一个工具。一般指实现了迭代器协议(即 和 方法)的类实例,判断标准是

generator: 生成器,是一种迭代器,使用yield关键字实现。(此处仅作对比,不考虑生成器的其他用法)

sequence: 序列,指实现了序列协议的类实例,内置的包括:

iter(obj): 一个内置函数,可以返回传入obj对应的迭代器,这里要求obj必须实现了迭代器协议或序列协议。

详细解读

iterable 可迭代对象

首先是一个令人难受的消息,可迭代对象的判别标准,到现在都没有一个优雅的方式,我们只能说:

可以运行 不报错的,或者可以运行 而不报错的,就是可迭代对象

参见帖子: https://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable

这里面的逻辑是这样的: 里,会调用iter函数返回迭代器,然后使用迭代器进行迭代。iter函数会根据两个协议得到一个迭代器返回,两个协议如下:

第一种,实现了 方法的,因为这个方法返回的就是迭代器,iter可以直接使用这个迭代器

第二种,实现了 ,并且是数字索引,并且是从0开始依次+1的数字索引的,iter函数会构造一个迭代器返回。代码大概是这样,这里写yield是为了表述方便,实际代码应该是用C语言写的

因为这里只except了IndexError,所以可以避免对只实现了 的mapping类对象的误判,举个例子

为了下面表述方便,我将实现了第一类协议的iterable称呼为 ,实现了第二类协议的称呼为

对一类iterable,可以有一种比较好的判断方式

还有一个需要注意的是,可迭代对象不一定是有序的。比如字符串就一定有序,集合就一定无序。

iterator 迭代器

参见官网文档: https://docs.python.org/2/library/stdtypes.html#iterator-types

文档里指出,只要实现了迭代器协议的,就算是迭代器。也就是只要正确实现了 和 (python3里是 ),就是迭代器。这里要求 可以不断取到下一个,而迭代器的 一定返回自身

普通迭代器在使用时,基本就是疯狂调用next,直到抛出StopIteration异常。下面代码里写yield同样只为表述方便,并不是实际代码

需要注意的很重要的一点是,迭代器是可耗尽的,也就是一次性的。举例来说,如下代码,a是一个迭代器,第一次循环的时候可以正常使用,输出结果,第二次使用时,因为迭代器已耗尽,所以什么都输出不了,这时候如果再调动一下a.next(),就必定会报 了

generator 生成器

参见官网: https://docs.python.org/2/library/stdtypes.html#generator-types

生成器也是一种迭代器,那么它和一般迭代器相比,有什么优势?

我认为优势有三点

第一点是,生成器比迭代器多实现了三个方法,send, throw和close,提供了更多操作,这里不再展开。

第二点是,生成器更方便,生成器有两种构造方式,1是函数类,用yield关键字构建,2是列表推导的时候,把列表推导最外层的 换成 ,这两种方式中 和 这两个函数都不需要显式实现,而是由生成器自动提供,可以让代码更pythonic,也更易读

第三点是,生成器更适合做计算,即含有复杂逻辑的迭代。我们知道普通迭代器不停next就完了,而生成器一般要求在next之前需要做各种计算和判断,然后返回合适的值。虽然这种操作使用迭代器也能实现,但跟生成器相比实在是不方便。举个最经典的计算斐波那契数列的例子,下面是迭代器实现和生成器实现。

其实,生成器一般不跟迭代器做比较,生成器比较的是类似列表推导一类的,先把全量数据生成出来,再依次迭代的情况,那种情况会消耗大量内存空间,而生成器消耗的空间是一定的,跟size没关系。最经典的例子是 和 ,后者的 是用的生成器实现,这样在迭代时,就不会像前者一样先把整个list算出来再迭代,而是迭代一个算一个,在数据量超大的时候,后者的优势会极为明显。

如下面两行,同样是输出0~1亿序列中的前三位,第一行会先把这一亿个数算出来,存到list里,然后再摘前三个,速度超慢还浪费空间。第二行则是一共就生成了三个,时间空间节省的不是一点半点。注意,生成器不是序列,所以不能用切片功能,我这里用的 可以对迭代器进行切片,返回的仍然是迭代器,所以最后要用list包一层,消耗尽迭代器。

sequence 序列

参加官网文档: https://docs.python.org/2/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange

序列,是指有顺序的,有数字索引的,且索引是从0到序列长度-1的,不是mapping的一种结构。python里就只有六种: ,自定义的类如果想通过sequence检测,要么继承 ,要么继承Sequence,要么使用 ,没有像迭代器那样实现几个方法就可以成为迭代器的操作。我们常用的其实也就是字符串,列表,元组以及xrange。这个xrange本身是sequence,但它的 是返回的生成器,这个需要注意下。

我们平时一般也不需要去判断一个obj是不是序列,如果非要判断的话,可以参考这个帖子: https://stackoverflow.com/questions/43566044/what-is-pythons-sequence-protocol

这些概念之间的关系

这次写的好像有点麻烦,已经在尽量讲清楚了,但是有些地方感觉还是找不到合理的表述方式,应该是理解还不够到位吧...

继续求意见~

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180731G0C4SL00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券