当谈论迭代器时,我谈些什么?
作者 | 樱雨楼
编辑 | 豌豆花下猫
转载自python猫(ID:python_cat)
导语:之前说过,我对于编程语言跟其它学科的融合非常感兴趣,但我还说漏了一点,就是我对于 Python 跟其它编程语言的对比学习,也很感兴趣。所以,我一直希望能聚集一些有其它语言基础的同学,一起讨论共通的语言特性间的话题。不同语言的碰撞,常常能带给人更高维的视角,也能触及到语言的根基,这个过程是极有益的。
0 前言
迭代器(Iterator)是 Python 以及其他各种编程语言中的一个非常常见且重要,但又充满着神秘感的概念。无论是 Python 的基础内置函数,还是各类高级话题,都处处可见迭代器的身影。
那么,迭代器究竟是怎样的一个概念?其又为什么会广泛存在于各种编程语言中?本文将基于 C++ 与 Python,深入讨论这一系列问题。
1 什么是迭代器?我们为什么要使用迭代器?
什么是迭代器?当我初学 Python 的时候,我将迭代器理解为一种能够放在“for xxx in …”的“…”位置的东西;后来随着学习的深入,我了解到迭代器就是一种实现了迭代器协议的对象;学习 C++ 时,我了解到迭代器是一种行为和指针类似的对象…
事实上,迭代器是一个伴随着迭代器模式(Iterator Pattern)而生的抽象概念,其目的是分离并统一不同的数据结构访问其中数据的方式,从而使得各种需要访问数据结构的函数,对于不同的数据结构可以保持相同的接口。
在很多讨论 Python 迭代器的书籍与文章中,我看到这样两种观点:1. 迭代器是为了节约数据结构所产生的内存;2. 遍历迭代器效率更高。
这两点论断都是很不准确的:首先,除了某些不定义在数据结构上的迭代器(如文件句柄,itertools 模块的 count、cycle 等无限迭代器等),其他迭代器都定义在某种数据结构上,所以不存在节约内存的优势;其次,由于迭代器是一种高度泛化的实现,其需要在每一次迭代器移动时都做一些额外工作(如 Python 需要不断检测迭代器是否耗尽,并进行异常监测;C++ 的 deque 容器需要对其在堆上用于存储的多段不连续内存进行衔接等),故遍历迭代器的效率一定低于或几乎接近于直接遍历容器,而不太可能高于直接遍历原容器。
综上所述,迭代器存在的意义,不是为了空间换时间,也不是为了时间换空间,而是一种适配器(Adapter)。迭代器的存在,使得我们可以使用同样的 for 语句去遍历各种容器,或是像 C++ 的 algorithm 模块所示的那样,使用同样的接口去处理各种容器。
这些容器可以是一个连续内存的数组或列表,或是一个多段连续内存的 deque,甚至是一个完全不连续内存的链表或是哈希表等等,我们完全不需要关注迭代器对于不同的容器究竟是怎么取得数据的。
2 C++中的迭代器
2.1 泛化指针
在 C++ 中,迭代器通过泛化指针(Generalized Pointer)的形式呈现。泛化指针与仿函数(Functor)的定义类似,其包含以下两种情况:
是一个真正的指针
不是指针,但重载了某些指针运算符(如“*,++,--,!=” 等),使得其行为和指针相似
根据泛化指针为了将其“伪装”成一个真正的指针从而重载的运算符的数量,迭代器被分为五种,如下文所示。
2.2 C++的迭代器分类
C++ 的迭代器按其所支持的行为被分为五类:
输入迭代器(Input Iterator):仅可作为右值(rvalue),不可作为左值(lvalue)。可以进行比较(“== 与 !=”)
输出迭代器(Output Iterator):仅可作为左值,不可作为右值
前向迭代器(Forward Iterator):支持一切输入迭代器的操作,以及单步前进操作(++)
双向迭代器(Bidirectional Iterator):支持一切前向迭代器的操作,以及单步后退操作(--)
随机访问迭代器(Random Access Iterator):支持一切双向迭代器操作,以及非单步双向移动操作
对于前向迭代器,双向迭代器,以及随机访问迭代器,如果其不存在底层 const(Low-Level Const)限定,则同时也支持一切输出迭代器操作。
2.3 迭代器适配器
C++ 中还存在一系列迭代器适配器,用于使得一些非迭代器对象的行为类似于迭代器,或修改迭代器的一些默认行为,大致包含如下几个类别:
插入迭代器(Insert Iterator):使得对迭代器左值的写入操作变为向容器中插入数据的操作,按插入位置的不同,可分为 front_insert_iterator,back_insert_iterator 和 insert_iterator
反向迭代器(Reverse Iterator):对调迭代器的移动方向。使得“+”操作变为向左移动,同时“-”操作变为向右移动(类似于 Python 的 reversed 函数)
移动迭代器(Move Iterator):使得对迭代器的取值变为右值引用(Rvalue Reference)
流迭代器(Stream Iterator):使流对象的行为适配迭代器(类似于 Python 的文件句柄)
3 Python中的迭代器
3.1 迭代器协议
在 Python 中,迭代器基于鸭子类型(Duck Type)下的迭代器协议(Iterator Protocol)实现。迭代器协议规定:如果一个类想要成为可迭代对象(Iterable Object),则其必须实现__iter__方法,且其返回值需要是一个实现了__next__方法的对象。
即:实现了__iter__方法的类将成为可迭代对象,而实现了__next__方法的类将成为迭代器。
显然,__iter__方法是 iter 函数所对应的魔法方法,__next__方法是 next 函数所对应的魔法方法。
对于一个可迭代对象,针对“谁实现了__next__方法?”这一问题进行讨论,可将可迭代对象的实现分为两种情况:
self 未实现__next__:如果__iter__方法的返回值就是一个 Iterator,则此时 self 即为一个可迭代对象。此时,self 将迭代操作“委托”到了另一个数据结构上。示例代码如下:
self 实现了__next__:如果__iter__方法返回 self,则说明 self 本身将作为迭代器,此时 self 本身需要继续实现__next__方法,以实现完整的迭代器协议。示例代码如下:
此例可以看出,当迭代器终止时,通过抛出 StopIteration 异常告知 Python 迭代器已耗尽。
3.2 生成器
生成器(Generator)是 Python 特有的一组特殊语法,其主要目的为提供一个基于函数而不是类的迭代器定义方式。同时,Python 也具有生成器推导式,其基于推导式语法快速建立迭代器。生成器一般适用于需要创建简单逻辑的迭代器的场合。
只要一个函数的定义中出现了 yield 关键词,则此函数将不再是一个函数,而成为一个“生成器构造函数”,调用此构造函数即可产生一个生成器对象。
由此可见,如果仅讨论该语法本身,而不关心实现的话:生成器只是“借用”了函数定义的语法,实际上与函数并无关系(并不代表生成器的底层实现也与函数无关)。示例代码如下:
生成器推导式则更为简单,只需要将列表推导式的中括号换为小括号即可:
综上所述,生成器是 Python 独有的一类迭代器的特殊构造方式。生成器一旦被构造,其会自动实现完整的迭代器协议。
3.3 无限迭代器
itertools 模块中实现了三个特殊的无限迭代器(Infinite Iterator):count,cycle 以及 repeat,其有别于普通的表示范围的迭代器。如果对无限迭代器进行迭代将导致无限循环,故无限迭代器通常只可使用 next 函数进行取值。
3.4 与C++迭代器的比较
经过上文的讨论可以发现,Python 只有一种迭代器,此种迭代器只能进行单向,单步前进操作,且不可作为左值。故 Python 的迭代器在 C++ 中应属于单向只读迭代器,这是一种很低级的迭代器。
此外,由于迭代器只支持单向移动,故一旦向前移动便不可回头,如果遍历一个已耗尽迭代器,则 for 循环将直接退出,且无任何错误产生,此种行为往往会产生一些难以察觉的 bug,实际使用时请务必注意。
综上所述,Python 对于迭代器的实现其实是高度匮乏的,应谨慎使用。
4 迭代器有效性
4.1 什么是迭代器有效性?
由于迭代器本身并不是独立的数据结构,而是指向其他数据结构中的值的泛化指针,故和普通指针一样,一旦指针指向的内存发生变动,则迭代器也将随之失效。
如果迭代器指向的数据结构是只读的,则显然,直到析构函数被调用,迭代器都不会失效。但如果迭代器所指向的数据结构在其存在时发生了插入或删除操作,则迭代器将可能失效。故讨论某个操作是否会导致指向容器的迭代器失效,是一个很重要的话题。
4.2 C++的迭代器有效性
由于 Python 中没有 C++ 的 list、deque 等数据结构实现,故本文只简单地讨论 vector 与 unordered_map 这两种数据结构的迭代器有效性。
对于 vector,由于其存在内存扩容与转移操作,故任何会潜在导致内存扩容的方法都将损坏迭代器,包括 push_back、emplace_back、insert、emplace 等。
unordered_map 与 vector 的情形类似,对 unordered_map 进行任何插入操作也将损坏迭代器。
4.3 Python的迭代器有效性
注:本节所讨论全部内容均基于实际行为进行猜想和推论,并没有经过对 Python 源代码的考察和验证,仅供读者参考。
4.3.1 尾插入操作不会损坏指向当前元素的List迭代器
考察如下代码:
如果在 C++ 中对一个 vector 执行这么多次的 push_back,则指向第二个元素的迭代器一定早已失效。但在 Python 中可以看到,指向 List 的迭代器并未失效,其仍然返回了 2。
故可猜想:Python 对于 List 所产生的迭代器并不跟踪指向 List 元素的指针,而仅仅跟踪的是容器的索引值。
4.3.2 尾插入操作会损坏List尾迭代器
首先,Python 不存在尾迭代器这一概念。但由上述代码可知,当迭代器所指向的 List 变长后,迭代器的终止点也随之变化,即:原先的尾迭代器将不再适用。
按照“迭代器仅跟踪元素索引值”这一推断,也能解释这一行为。
4.3.3 迭代器一旦耗尽,则将永久损坏
考察如下代码:
当 for 一个迭代器后,迭代器将耗尽,在 C++ 中,这将导致头尾迭代器相等,但由上述代码可知, Python 的迭代器一旦耗尽,便不再可以使用,即使继续往容器中增加元素也不行。
由此可见, Python 的迭代器中可能存在某种用于指示迭代器是否被耗尽的标记,一旦迭代器被标记为耗尽状态,便永远不可继续使用了。
4.3.4 任何插入操作都将损坏Dict迭代器
考察如下代码:
当对一个 Dict 进行插入操作后,原 Dict 迭代器将立即失效,并抛出 RuntimeError。这与 C++ 中的行为是一致的,且更为安全。
Set 与 Dict 具有相同的迭代器失效性质,不再重复讨论。
5 后记
迭代器的故事到这里就结束了。总的看来,Python 中的迭代器虽应用广泛,但并不是一种高级的,灵活的实现,且存在着一些黑魔法。故唯有深入的去理解,才能真正的用好迭代器。祝编程愉快~
(*本文为 AI科技大本营转载文章,转载请联系原作者)
◆
精彩推荐
◆
“只讲技术,拒绝空谈!”2019 AI开发者大会将于9月6日-7日在北京举行,这一届AI开发者大会有哪些亮点?一线公司的大牛们都在关注什么?AI行业的风向是什么?2019 AI开发者大会,倾听大牛分享,聚焦技术实践,和万千开发者共成长。
目前,大会早鸟票限量发售中~扫码购票,领先一步!
推荐阅读
江湖又现中科大少年班的传说
什么限制了GNN的能力?首篇探究GNN普适性与局限性的论文出炉!2019年最新华为、BAT、美团、头条、滴滴面试题目及答案汇总
10分钟学会用Pandas做多层级索引
中国第一程序员,微软得不到他就要毁了他!
透析《长安十二时辰》里的望楼,人类在唐朝就有 5G 愿望了?
首批 8 款 5G 手机获 3C 认证;iPhone6 系列停产;Android Q Beta 5 发布 | 极客头条
"别太乐观, 冲破黑暗还很远呀! "

相关文章:

Windows7在Eclipse中配置Python+OpenCV
1. 从http://www.oracle.com/technetwork/java/javase/downloads/jdk-7u2-download-1377129.html下载jdk-7u2-windows-i586.exe,安装到D:\ProgramFiles\Java,并将D:\ProgramFiles\Java\jdk1.7.0_02\bin添加到环境变量中; 2. 从…

Pinterest基于AWS规模化使用Apache Kafka的实践经验
在Pinterest,Apache Kafka被用于为实时流应用程序传输数据、记录日志和可视化监控指标。Pinterest的Kafka托管在AWS上,为了实现复制和高可用性,其安装使用了MirrorMaker和DoctorKafka工具。 Pinterest的技术主管Yu Yang写道,Pinte…

Open×××以及其它IP层×××的完全链路层处理的实现
如果Open也能实现传输模式该有多好,如果基于Open实现的产品能仅仅作为一根昂贵的网线串接在用户网络环境,自动捕获感兴趣流量该有多好;如果它能做到只需要配置一个IP即可工作而无需配置任何路由该有多好。我们知道Open是一个用户态的程序&…

Windows 7 64位机上OpenCV2.4.3的编译、安装与配置
1. 从http://sourceforge.net/projects/opencvlibrary/files/opencv-win/2.4.3/下载OpenCV2.4.3; 2. 将OpenCV-2.4.3.exe放到D:\soft\OpenCV2.4.3文件夹下,解压到当前文件夹下,生成一个opencv文件夹; 3. 下载并安…

有望替代卷积神经网络?微软最新研究提基于关系网络的视觉建模
导语:最近两年,自注意力机制、图和关系网络等模型在NLP领域刮起了一阵旋风,基于这些模型的Transformer、BERT、MASS等框架已逐渐成为NLP的主流方法。这些模型在计算机视觉领域是否能同样有用呢?近日,微软亚洲研究院视觉…

Word 2013无法发布文章到博客园
2018年12月12日突然发现word2013无法发布文章到博客园了, 虽然不常发布博客, 但作为一个强迫症患者, 不折腾好了, 吃肉都不香呀! 删除之前的账户, 想重新注册, 居然遇到了灰色对话框!…

1 sec on Large Judge (java): https://github.com/l...
1 sec on Large Judge (java): https://github.com/leoyonn/leetcode/blob/master/src/q029_substring_of_all_words/Solution.java转载于:https://www.cnblogs.com/codingtmd/archive/2013/03/31/5079017.html

性能提升3倍的树莓派4,被爆设计缺陷!
整理 | 屠敏转载自CSDN(ID:CSDNnews)一直以来,素有世界最小电脑之称的 Raspberry Pi(树莓派)是一种独特的存在。它不仅只有一块信用卡般的体积,还具备主机电脑所具备的功能,如运行 L…

Windows7 64位机上Emgu CV2.4.2安装与配置
1. 从http://sourceforge.net/projects/emgucv/?sourcedirectory下载最新的Emgu CV2.4.2; 2. 将libemgucv-windows-x86-gpu-2.4.2.1777拷贝到D:\soft\Emgu2.4.2文件夹下,运行此.exe文件,将其安装到D:\soft\Emgu2.4.2\emgucv-wind…

2018年12月,华为HCNP大面积更新题目,军哥独家解题咯
2018年12月,华为HCNP大面积更新题目,乾颐堂军哥独家解题咯2018年是华为认证变动比较大的一年,华为认证走过这几年不得不说是有一定进步的,而且最近华为孟女侠确实让我也小小的骄傲了一把,所以当然希望华为认证能做的更…

关于ProGuard的学习了解(从别处转来)
from:http://www.cnitblog.com/zouzheng/archive/2011/01/12/72639.html在Android项目中用到JNI,当用了proguard后,发现native方法找不到很多变量,原来是被produard优化掉了。所以,在JNI应用中该慎用progurad啊。解决办…

tesseract-ocr3.02字符识别过程操作步骤
1、 从http://code.google.com/p/tesseract-ocr/downloads/list下载tesseract-ocr-3.02-vs2008、tesseract-ocr-3.02.chi_sim.tar、tesseract-ocr-3.02.02.tar、tesseract-ocr-3.02.02-doc-html.tar、leptonica-1.68-win32-lib-include-dirs相关文件; 2、 将所有…

中文repo“霸榜”GitHub Trending,国外开发者不开心了
编译整理 | 一一出品 | AI科技大本营(ID:rgznai100)近日,一位叫Balazs Saros 的国外开发者在Medium上发表了一篇名为"Chinese repos are ruining the Github trending page"的博文,翻译一下他的意思就是“中文 repo 正在…

使用 electron-updater 自动更新应用
前端工程师可以使用 Electron 非常方便的编写出 PC 端应用,而应用更新的方式也有很多,详细可见更新应用程序。 我的项目是基于 electron-vue 搭建的,构建打包生成安装包,则用的是 electron-builder,所以更新自然选择 e…

struts2请求处理过程源代码分析(1)
2019独角兽企业重金招聘Python工程师标准>>> 转载自:http://www.see-source.com/ 源码解析网 网上对于struts2请求处理流程的讲解还是比较多的,有的还是非常详细的,所以这里我就简单地将大概流程总结下,有了个大概印象…

Ubuntu中C代码静态检查工具Splint的安装配置和使用
1、 从http://www.splint.org/download.html下载splint-3.1.2.src.tgz,存放到/home/spring/Splint文件夹下; 2、 打开终端; 3、 解压缩:tar zxvfsplint-3.1.2.src.tgz 4、 安装到/usr/local/splint目录下: …

Fetch 入门
一、什么是Fetch ? Fetch的定义 Fetch本质上是一种标准,该标准定义了请求、响应和绑定的流程。 Fetch标准还定义了Fetch () JavaScript API,它在相当低的抽象级别上公开了大部分网络功能,我们今天讲的主要是Fetch API。Fetch API …

保障数据安全,强调科技向善,旷视发布《人工智能应用准则》
目录 AI应用落地加速 善用科技是关键 《人工智能应用准则》全文 2019年7月17日,旷视正式全文公布基于企业自身管理标准的《人工智能应用准则》(以下简称《准则》)。《准则》从正当性、人的监督、技术可靠性和安全性、公平和多样性、问责和及…

胜者树和败者树 - qianye0905 - 博客园
胜者树和败者树 - qianye0905 - 博客园胜者树和败者树胜者树和败者树都是完全二叉树,是树形选择排序的一种变型。每个叶子结点相当于一个选手,每个中间结点相当于一场比赛,每一层相当于一轮比赛。不同的是,胜者树的中间结点记录的…

C/C++代码静态检查工具PC-lint在VS2008开发环境中的安装配置和使用
PC-Lint偏重于代码的逻辑分析,它能够发现代码中潜在的错误,比如数组访问越界、内存泄漏、使用未初始化变量等。 1、 从http://download.csdn.net/detail/liuchang5/3005191 下载破解版PC-lint9.0; 2、 解压缩到D:\soft\PC-lint,…
k8s使用kube-router网络插件并监控流量状态
简介 kube-router是一个新的k8s的网络插件,使用lvs做服务的代理及负 载均衡,使用iptables来做网络的隔离策略。部署简单,只需要在每个节点部署一个daemonset即可,高性能,易维护。支持pod间通信,以及服务的代…

作业盒子完成1.5亿美元D轮融资,更名“小盒科技”
作者 | 夕颜 导读:2019 年 7 月 18 日,AI 在线教育创企“作业盒子”召开发布会,宣布已于今年 5 月完成 1.5 亿美元 D 轮融资,由阿里巴巴领投。同时,“作业盒子”宣布进行品牌升级,正式更名为“小盒科技”&a…

8500WN流畅高速上网高端卡 12核心不锁倍频
据台湾媒体最新报道,台湾无线网卡厂商最新推出一款大功率80DBI无线网卡-横空出世8500WN集成机。售价约1180新台币(折合人民币约298元) 台湾卡王是全球著名的大功率无线网卡生产厂商,2007年曾最早推出大功率无线网卡8G,以其卓越的品质…

Fiddler抓包工具总结(转)
序章 Fiddler是一个蛮好用的抓包工具,可以将网络传输发送与接受的数据包进行截获、重发、编辑、转存等操作。也可以用来检测网络安全。反正好处多多,举之不尽呀!当年学习的时候也蛮费劲,一些蛮实用隐藏的小功能用了之后就忘记了&a…

Windows 64位机上C/C++代码静态检查工具Logiscope RuleChecker的安装和使用
1、 从http://download.csdn.net/detail/zmywly/3611820 和 http://download.csdn.net/detail/zmywly/3611854下载破解版; 2、 将文件解压缩到D:\soft\logiScope文件夹下,会生成一个logiScope[6.1.30]文件夹; 3、 双击D:\soft\lo…

作业盒子完成1.5亿美元D轮融资,用AI普及教育资源
作者 | 夕颜出品 | AI科技大本营(ID:rgznai100)导读:2019 年 7 月 18 日,AI 在线教育创企“作业盒子”召开发布会,宣布已于今年 5 月完成 1.5 亿美元 D 轮融资,由阿里巴巴领投。同时,“作业盒子…

迭代器接口IteratorAggregate 与类 ArrayIterator(转)
也许你很想使用foreach来遍历一个类中的属性,然而你却没有很好的方式来这么做。可能使用PHP中class的操作的方式能够帮助你实现一些,但是现在我想你有了更好的方式。通过继承接口:IteratorAggregate来实现。 示例 [php] view plaincopy <?…

整理《Mastering OpenCV with Practical Computer Vision Projects》中第8章用Eigenfaces或Fisherfaces进行人脸识别操作流程
These generally involve four main steps:(1)、Face detection;(2)、Face preprocessing;(3)、Collect and learn faces;(4)、Face recognition. 一、Face detection(Haar-based、LBP-based) LBP-based detectors are potential…

性能比GPU高100倍!华人教授研发全球首个可编程忆阻器AI计算机
译者 | 陆离责编 | 夕颜出品 | AI科技大本营(ID:rgznai100)导读:近日,密歇根大学研发成功第一台可编程的忆阻器计算机,它不仅是一个通过外部计算机运行的忆阻器阵列,而且还是可以在智能手机等小型设备上进行…

深入解析redis cluster gossip机制
社区版redis cluster是一个P2P无中心节点的集群架构,依靠gossip协议传播协同自动化修复集群的状态。本文将深入redis cluster gossip协议的细节,剖析redis cluster gossip协议机制如何运转。协议解析 cluster gossip协议定义在在ClusterMsg这个结构中&am…