当谈论迭代器时,我谈些什么?

花下猫语:之前说过,我对于编程语言跟其它学科的融合非常感兴趣,但我还说漏了一点,就是我对于 Python 跟其它编程语言的对比学习,也很感兴趣。所以,我一直希望能聚集一些有其它语言基础的同学,一起讨论共通的语言特性间的话题。不同语言的碰撞,常常能带给人更高维的视角,也能触及到语言的根基,这个过程是极有益的。

这篇文章是群内 樱雨楼 小姐姐的投稿,她是我们学习群里的真·大佬,说到对 Python 的研究以及高阶知识的水平,无人能出其右(群里很多同学都被她实力圈粉啦)。除了 Python,她对 C++、Perl、Go 与 Fortran 等语言都有涉猎,本文主要是对比了 Python 与 C++,来深入谈谈迭代器。话不多说,请看正文。

68b02e3bly1g4etmmaofqj22bk1jkqkz.jpg

樱雨楼 | 原创作者

豌豆花下猫 | 编辑润色

本文原创并首发于公众号【Python猫】,未经授权,请勿转载。

原文地址:https://mp.weixin.qq.com/s/Be4tHnR0BY-C__xoPPBjhQ

0 前言

迭代器(Iterator)是 Python 以及其他各种编程语言中的一个非常常见且重要,但又充满着神秘感的概念。无论是 Python 的基础内置函数,还是各类高级话题,都处处可见迭代器的身影。

那么,迭代器究竟是怎样的一个概念?其又为什么会广泛存在于各种编程语言中?本文将基于 C++ 与 Python,深入讨论这一系列问题。

1 什么是迭代器?我们为什么要使用迭代器?

什么是迭代器?当我初学 Python 的时候,我将迭代器理解为一种能够放在“for xxx in ...”的“...”位置的东西;后来随着学习的深入,我了解到迭代器就是一种实现了迭代器协议的对象;学习 C++ 时,我了解到迭代器是一种行为和指针类似的对象...

事实上,迭代器是一个伴随着迭代器模式(Iterator Pattern)而生的抽象概念,其目的是分离并统一不同的数据结构访问其中数据的方式,从而使得各种需要访问数据结构的函数,对于不同的数据结构可以保持相同的接口。

在很多讨论 Python 迭代器的书籍与文章中,我看到这样两种观点:1. 迭代器是为了节约数据结构所产生的内存;2. 遍历迭代器效率更高。

这两点论断都是很不准确的:首先,除了某些不定义在数据结构上的迭代器(如文件句柄,itertools 模块的 count、cycle 等无限迭代器等),其他迭代器都定义在某种数据结构上,所以不存在节约内存的优势;其次,由于迭代器是一种高度泛化的实现,其需要在每一次迭代器移动时都做一些额外工作(如 Python 需要不断检测迭代器是否耗尽,并进行异常监测;C++ 的 deque 容器需要对其在堆上用于存储的多段不连续内存进行衔接等),故遍历迭代器的效率一定低于或几乎接近于直接遍历容器,而不太可能高于直接遍历原容器。

68b02e3bly1g4eufzzhiyg20b405w14x.gif

综上所述,迭代器存在的意义,不是为了空间换时间,也不是为了时间换空间,而是一种适配器(Adapter)。迭代器的存在,使得我们可以使用同样的 for 语句去遍历各种容器,或是像 C++ 的 algorithm 模块所示的那样,使用同样的接口去处理各种容器。

这些容器可以是一个连续内存的数组或列表,或是一个多段连续内存的 deque,甚至是一个完全不连续内存的链表或是哈希表等等,我们完全不需要关注迭代器对于不同的容器究竟是怎么取得数据的。

2 C++中的迭代器

2.1 泛化指针

在 C++ 中,迭代器通过泛化指针(Generalized Pointer)的形式呈现。泛化指针与仿函数(Functor)的定义类似,其包含以下两种情况:

  1. 是一个真正的指针
  2. 不是指针,但重载了某些指针运算符(如“*,++,--,!=” 等),使得其行为和指针相似

根据泛化指针为了将其“伪装”成一个真正的指针从而重载的运算符的数量,迭代器被分为五种,如下文所示。

2.2 C++的迭代器分类

C++ 中,迭代器按照其所支持的行为被分为五类:

  1. 输入迭代器(Input Iterator):仅可作为右值(rvalue),不可作为左值(lvalue)。可以进行比较(“== 与 !=”)
  2. 输出迭代器(Output Iterator):仅可作为左值,不可作为右值
  3. 前向迭代器(Forward Iterator):支持一切输入迭代器的操作,以及单步前进操作(++)
  4. 双向迭代器(Bidirectional Iterator):支持一切前向迭代器的操作,以及单步后退操作(--)
  5. 随机访问迭代器(Random Access Iterator):支持一切双向迭代器操作,以及非单步双向移动操作

对于前向迭代器,双向迭代器,以及随机访问迭代器,如果其不存在底层 const(Low-Level Const)限定,则同时也支持一切输出迭代器操作。

2.3 迭代器适配器

C++ 中还存在一系列迭代器适配器,用于使得一些非迭代器对象的行为类似于迭代器,或修改迭代器的一些默认行为,大致包含如下几个类别:

  1. 插入迭代器(Insert Iterator):使得对迭代器左值的写入操作变为向容器中插入数据的操作,按插入位置的不同,可分为 front_insert_iterator,back_insert_iterator 和 insert_iterator
  2. 反向迭代器(Reverse Iterator):对调迭代器的移动方向。使得“+”操作变为向左移动,同时“-”操作变为向右移动(类似于 Python 的 reversed 函数)
  3. 移动迭代器(Move Iterator):使得对迭代器的取值变为右值引用(Rvalue Reference)
  4. 流迭代器(Stream Iterator):使流对象的行为适配迭代器(类似于 Python 的文件句柄)

3 Python中的迭代器

3.1 迭代器协议

在 Python 中,迭代器基于鸭子类型(Duck Type)下的迭代器协议(Iterator Protocol)实现。迭代器协议规定:如果一个类想要成为可迭代对象(Iterable Object),则其必须实现__iter__方法,且其返回值需要是一个实现了__next__方法的对象。即:实现了__iter__方法的类将成为可迭代对象,而实现了__next__方法的类将成为迭代器。

显然,__iter__方法是iter函数所对应的魔法方法,__next__方法是 next 函数所对应的魔法方法。

68b02e3bly1g4eumghtomj20sg0fe3zb.jpg

对于一个可迭代对象,针对“谁实现了__next__方法?”这一问题进行讨论,可将可迭代对象的实现分为两种情况:

  1. self 未实现__next__:如果__iter__方法的返回值就是一个 Iterator,则此时 self 即为一个可迭代对象。此时,self 将迭代操作“委托”到了另一个数据结构上。示例代码如下:
class SampleIterator:def __iter__(self):return iter(...)
  1. self 实现了__next__:如果__iter__方法返回 self,则说明 self 本身将作为迭代器,此时 self 本身需要继续实现__next__方法,以实现完整的迭代器协议。示例代码如下:
class SampleIterator:def __iter__(self):return selfdef __next__(self):# Not The Endif ...:return ...# Reach The Endelse:raise StopIteration

此示例中可以看出,当迭代器终止时,通过抛出 StopIteration 异常告知 Python 迭代器已耗尽。

3.2 生成器

生成器(Generator)是 Python 特有的一组特殊语法,其主要目的为提供一个基于函数而不是类的迭代器定义方式。同时,Python 也具有生成器推导式,其基于推导式语法快速建立迭代器。生成器一般适用于需要创建简单逻辑的迭代器的场合。

只要一个函数的定义中出现了 yield 关键词,则此函数将不再是一个函数,而成为一个“生成器构造函数”,调用此构造函数即可产生一个生成器对象。

由此可见,如果仅讨论该语法本身,而不关心实现的话:生成器只是“借用”了函数定义的语法,实际上与函数并无关系(并不代表生成器的底层实现也与函数无关)。示例代码如下:

def SampleGenerator():yield ...yield ...yield ...

生成器推导式则更为简单,只需要将列表推导式的中括号换为小括号即可:

(... for ... in ...)

综上所述,生成器是 Python 独有的一类迭代器的特殊构造方式。生成器一旦被构造,其会自动实现完整的迭代器协议。

3.3 无限迭代器

itertools 模块中实现了三个特殊的无限迭代器(Infinite Iterator):count,cycle 以及 repeat,其有别于普通的表示范围的迭代器。如果对无限迭代器进行迭代将导致无限循环,故无限迭代器通常只可使用 next 函数进行取值。

关于无限迭代器的详细内容,可参阅 Python 文档。(注:旧文 Python进阶:设计模式之迭代器模式 也介绍过)

3.4 与C++迭代器的比较

经过上文的讨论可以发现,Python 只有一种迭代器,此种迭代器只能进行单向,单步前进操作,且不可作为左值。故 Python 的迭代器在 C++ 中应属于单向只读迭代器,这是一种很低级的迭代器。

此外,由于迭代器只支持单向移动,故一旦向前移动便不可回头,如果遍历一个已耗尽迭代器,则 for 循环将直接退出,且无任何错误产生,此种行为往往会产生一些难以察觉的 bug,实际使用时请务必注意。

综上所述,Python 对于迭代器的实现其实是高度匮乏的,应谨慎使用。

4 迭代器有效性

4.1 什么是迭代器有效性?

由于迭代器本身并不是独立的数据结构,而是指向其他数据结构中的值的泛化指针,故和普通指针一样,一旦指针指向的内存发生变动,则迭代器也将随之失效。

如果迭代器指向的数据结构是只读的,则显然,直到析构函数被调用,迭代器都不会失效。但如果迭代器所指向的数据结构在其存在时发生了插入或删除操作,则迭代器将可能失效。故讨论某个操作是否会导致指向容器的迭代器失效,是一个很重要的话题。

68b02e3bly1g4eulthd9cj21hc0swgp2.jpg

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迭代器

考察如下代码:

numList = [1, 2, 3]
numListIter = iter(numList)
next(numListIter)for i in range(1000000):numList.append(i)# print 2
print(next(numListIter))

如果在 C++ 中对一个 vector 执行这么多次的 push_back,则指向第二个元素的迭代器一定早已失效。但在 Python 中可以看到,指向 List 的迭代器并未失效,其仍然返回了 2。

故可猜想:Python 对于 List 所产生的迭代器并不跟踪指向 List 元素的指针,而仅仅跟踪的是容器的索引值。

4.3.2 尾插入操作会损坏List尾迭代器

numList = [1,2]
numListIter = iter(numList)# 1
next(numList)
numList.append(3)# 2 
next(numListIter)# 3
print(next(numListIter))

首先,Python 不存在尾迭代器这一概念。但由上述代码可知,当迭代器所指向的 List 变长后,迭代器的终止点也随之变化,即:原先的尾迭代器将不再适用。

按照“迭代器仅跟踪元素索引值”这一推断,也能解释这一行为。

4.3.3 迭代器一旦耗尽,则将永久损坏

考察如下代码:

numList = [1,2]
numListIter = iter(numList)
for _ in numListIter:passnumList.append(3)# StopIteration
print(next(numListIter))

当完整的 for 一个迭代器后,迭代器将耗尽,在 C++ 中,这将导致头尾迭代器相等,但由上述代码可知, Python 的迭代器一旦耗尽,便不再可以使用,即使继续往容器中增加元素也不行。

由此可见, Python 的迭代器中可能存在某种用于指示迭代器是否被耗尽的标记,一旦迭代器被标记为耗尽状态,便永远不可继续使用了。

68b02e3bly1g4euo8ldevj20sg0f4wfh.jpg

4.3.4 任何插入操作都将损坏Dict迭代器

考察如下代码:

numDict = {1:2}
numDictIter = iter(numDict)
numDict[3] = 4# RuntimeError
next(numDictIter)

当对一个 Dict 进行插入操作后,原 Dict 迭代器将立即失效,并抛出 RuntimeError。这与 C++ 中的行为是一致的,且更为安全。

Set 与 Dict 具有相同的迭代器失效性质,不再重复讨论。

5 后记

迭代器的故事到这里就结束了。总的看来,Python 中的迭代器虽应用广泛,但并不是一种高级的,灵活的实现,且存在着一些黑魔法。 故唯有深入的去理解,才能真正的用好迭代器。祝编程愉快~

(花下猫注:鉴于有同学看完本文,可能想要加群交流,我补充两句。我们群虽然是免费群,但一直想走高质量的技术交流路线,因此既限制人数,也严审核。公众号菜单栏有我联系方式,感兴趣的同学欢迎查看了解。)

68b02e3bly1g2aiq1kpa8j21hc0nmgs4.jpg

公众号【Python猫】, 本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、技术写作、优质英文推荐与翻译等等,欢迎关注哦。

转载于:https://www.cnblogs.com/pythonista/p/11094501.html

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

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

相关文章

在Vue-cli项目中使用echarts

该示例使用 vue-cli 脚手架搭建 安装echarts依赖 npm install echarts -S11 或者使用国内的淘宝镜像: 安装 npm install -g cnpm --registryhttps://registry.npm.taobao.org11 使用 cnpm install echarts -S11 创建图表 全局引入 main.js // 引入echarts im…

通过GitHub Pages创建个人主页

登陆github,创建新仓库,写入名字, 这里要以github.io做后缀, 不然创建出来的不是GitHub Pages 打开终端, cd到自己想要的文件夹后clone到本地 git clone https://github.com/username/username.github.io 进入这个项目文件夹 cd username.github.io 把写好HTML项目拷…

validate+jquery+ajax表单验证

1.案例 1.1 Html form表单内容 <form class"cForm" id"cForm" method"post" action""> <p> <label for"user">用户名</label> <input id"user" name"user" required minlen…

设置Maven下载镜像源(直接替换其中的 settings.xml 内容即可)

<?xml version"1.0" encoding"UTF-8"?> <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/SETTINGS/1.0.…

P1576 最小花费

----------------------------------- 这道题就是图论最短路&#xff0c;但是我们要改一下一些细节 比如说&#xff0c;因为这是算汇率&#xff0c;我们的初始化就要是0 我们还要改一改松弛操作 ----------------------------------- 还有&#xff0c;题目上给的是汇率&#xf…

css hack技术整理

做前端多年&#xff0c;虽然不是经常需要hack&#xff0c;但是我们经常会遇到各浏览器表现不一致的情况。基于此&#xff0c;某些情况我们会极不情愿的使用这个不太友好的方式来达到大家要求的页面表现。我个人是不太推荐使用hack的&#xff0c;要知道一名好的前端&#xff0c;…

Hanoi双塔问题

Hanoi双塔问题 题目描述 给定A,B,C三根足够长的细柱&#xff0c;在A柱上放有2n个中间有空的圆盘&#xff0c;共有n个不同的尺寸&#xff0c;每个尺寸都有两个相同的圆盘&#xff0c;注意这两个圆盘是不加区分的(下图为n3的情形&#xff09;。现要将 这些国盘移到C柱上&#xff…

vue中config/index.js:配置的详细理解

当我们需要和后台分离部署的时候&#xff0c;必须配置config/index.js: 用vue-cli 自动构建的目录里面 &#xff08;环境变量及其基本变量的配置&#xff09; 123456789101112131415var path require(path)module.exports {build: {index: path.resolve(__dirname, dist/ind…

uni-app吸顶固定样式

<template><view class"full"><view class"sticky-box"><!-- 搜索 --><uni-search-bar class"unisearchbar" radius"5" placeholder"请输入搜索关键词" clearButton"auto" bgColor&qu…

uni-app商品分类

<template><view class"classify"><!-- 分类部分 --><view class"cate-left"><view :class"[cate-item,activeIndexindex?active:]" v-for"(item,index) in cateList" :key"index"click"c…

uni-app微信小程序一键登录获取权限功能

<button class"bottom size_30" type"primary" lang"zh_CN" click"getUserInfo">一键登录</button>//第一授权获取用户信息》按钮触发getUserInfo() {// 展示加载框uni.showLoading({title: 加载中,});uni.getUserProfile…

第九章 结构体与共用体

姓名&#xff1a;吕家浩 实验地点&#xff1a;教学楼514教室 实验时间&#xff1a;4月30日 一、本章要点 1.通过实验理解结构体和共用体的数据结构2.结构体、共用体中数组的使用及变量的赋值3.结构体和共用体定义时的嵌套使用&#xff08;嵌套使用的结构体必须先定义&…

H5-localStorage数据存储总结

一、什么是localStorage、sessionStorage 在HTML5中&#xff0c;新加入了一个localStorage特性&#xff0c;这个特性主要是用来作为本地存储来使用的&#xff0c;解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k)&#xff0c;localStorage中一般浏览器支持的…

iOS开发-证书问题精析~

在iOS开发过程中&#xff0c;不可避免的要和证书打交道&#xff0c;真机调试、App上架、打包给测试去测试等都需要搞证书。在此过程中我们会遇到很多的问题&#xff0c;但是如果掌握了真机调试的原理和本质&#xff1b;遇到问题&#xff0c;我们就更容易定位问题之所在&#xf…

selenium+Java自动化

转载于:https://www.cnblogs.com/arvin-feng/p/11110483.html

npm升级package.json依赖包

使用npm管理node的包&#xff0c;可以使用npm update <name>对单个包升级&#xff0c;对于npm的版本大于 2.6.1,可以使用命令: npm install -g 升级全局的本地包。 对于版本小于2.6.1的一个一个包的升级实在是太麻烦&#xff0c;就想找到一个升级所有本地包的方法&#x…

Minimal coverage (贪心,最小覆盖)

题目大意&#xff1a;先确定一个M&#xff0c; 然后输入多组线段的左端和右端的端点坐标&#xff0c;然后让你求出来在所给的线段中能够 把[0, M] 区域完全覆盖完的最少需要的线段数&#xff0c;并输出这些线段的左右端点坐标。 思路分析&#xff1a; 线段区间的起点是0&#x…

vuex知识点

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式&#xff1b;集中存储和管理应用的所有组件状态。 状态&#xff1a;什么是状态&#xff0c;我们可以通俗的理解为数据。Vue只关心视图层&#xff0c;那么视图的状态如何来确定&#xff1f;我们知道是通过数据驱动&#xff0c…

[论文笔记]CVPR2017_Joint Detection and Identification Feature Learning for Person Search

Title: Joint Detection and Identification Feature Learning for Person Search; aXiv上该论文的第一个版本题目是 End-to-End Deep Learning for Person SearchAuthors: Tong Xiao1* ; Shuang Li1* ; Bochao Wang2 ; Liang Lin2; Xiaogang Wang1 Affilations: 1.The Chines…

.NetCore如何使用ImageSharp进行图片的生成

ImageSharp是对NetCore平台扩展的一个图像处理方案&#xff0c;以往网上的案例多以生成文字及画出简单图形、验证码等方式进行探讨和实践。 今天我分享一下所在公司项目的实际应用案例&#xff0c;导出微信二维码图片&#xff0c;圆形头像等等。 一、源码获取 Git项目地址&…