迭代是什么

迭代指的是一个重复的过程,每次重复都必须基于上一次的结果而继续,单纯的重复并不是迭代,如Python中的for循环就是一个非常好的迭代例子。

for item in range(10):print(item)

迭代必须向前推进,不能后退,如下所示:

# [0 , 1, 2, 3, 4, 5, 6, 7, 8, 9]
# ------------------------------>

下面这种方式就不属于迭代:

# [0 , 1, 2, 3, 4, 5, 6, 7, 8, 9]
# -------->
#     <----
#          --------------------->

迭代器协议

在学习迭代器的整个知识点中,迭代器协议占据了非常重要的位置。

迭代器协议中包含了2个最基本的概念,分别是可迭代对象和迭代器对象。

  • 可迭代对象(Iterable):内部实现了__iter__()方法的对象则被称为可迭代对象
  • 迭代器对象(Iterator):内部实现了__next__()方法的对象则被称之为迭代器对象

两者之间的关系:

  • 在Python中,迭代器对象一定属于可迭代对象范畴,也就说迭代器对象必须具有__iter__()方法以及__next__()方法
  • 在Python中,可迭代对象不一定属于迭代器对象范畴,也就是说可迭代对象只需要实现__iter__()方法即可

介绍2个函数:

  • iter(Object)函数,它底层会执行Object.__iter__()方法
  • next(Object)函数,它底层会执行Object.__next__()方法

内置类型

通过collections.abc下的Iterable类和Iterator类进行判定,可快速的判定出所有内置类型是否是一个可迭代对象或者迭代器对象:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:531509025
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
>>> from collections.abc import Iterable
>>> from collections.abc import Iterator>>> isinstance(list(), Iterable)
True
>>> isinstance(list(), Iterator)
False

经过测试,所有的容器类型(list、tuple、str、dict、set、frozenset)均属于可迭代对象,但不属于迭代器对象

原子类型(bool、int、float、None)等均不属于可迭代对象,更不属于迭代器对象。

也可以通过另一种方式进行验证,通过hasattr()函数,检查类中是否定义了某一个方法:

>>> hasattr(list,"__iter__")
True
>>> hasattr(list,"__next__")
False

for循环原理

当可迭代对象被for循环进行调用后,底层执行流程如下所示:

1.将自动的执行iter()方法,该方法内部会查找可迭代对象的__iter__()方法,如果具有该方法,则返回一个该可迭代对象的专属迭代器对象,如果没有该方法,则抛出TypeError object is not iterable的异常。

Ps:每次的for循环都会返回一个全新的迭代器对象

2.不断的调用迭代器对象的__next__()方法,并且返回迭代器对象中下一个数据项,当遍历完成整个迭代器后,引发Stopiteration异常终止迭代

Ps:迭代器本身并不存储任何数据项,存储的只是一个指针,该指针指向可迭代对象中真正存储的数据项,它指向当前被遍历到的数据项索引位置,下一次遍历则向后推进这个位置

3.for循环自动的捕捉Stopiteration异常,并且停止迭代

Ps:for循环底层就是while循环实现的,只不过多加了3个步骤:

第一步:执行可迭代对象的__iter()__方法并保存返回的专属迭代器

第二步:不断的执行迭代器的__next()__方法

第三步:捕获Stopiteration异常

我们手动的实现一个for循环:

li1 = list(range(10))iteratorObject = iter(li1)  # 1
while 1:try:print(next(iteratorObject))  # 2except StopIteration as e:  # 3break

1:执行可迭代对象的__iter__()方法并保存返回的专属迭代器

2:不断的执行迭代器的__next__()方法

3:捕获Stopiteration异常

线性可迭代对象与迭代器的实现

如果是一个线性容器的可迭代对象,那么它一定具有索引值,我们可以让它的__iter__()方法返回一个专属的迭代器对象。

然后专属迭代器对象中记录本次迭代遍历的索引值,根据这个索引值返回可迭代对象中的数据项,当索引值达到可迭代对象中数据项总个数-1的时候,抛出异常,本次迭代结束:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:531509025
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class linearTypeContainer:def __init__(self, array):if isinstance(array, list) or isinstance(array, tuple):self.array = arrayelse:raise TypeError("argument array must is linear container")def __iter__(self):return linearContainer_iterator(self.array)  # 1class linearContainer_iterator:def __init__(self, array):self.index = 0self.array = arrayself.len = len(self.array)def __next__(self):if self.index < self.len:retDataItem = self.array[self.index]self.index += 1return retDataItemelse:raise StopIterationdef __iter__(self):  # 2return selfcontainer = linearTypeContainer([i for i in range(10)])
for i in container:print(i)
print(list(container))

1:Python中的一切传参均为引用传递

故linearTypeContainer中的self.array和linearContainer_iterator的self.array都是一个对象,并不会额外开辟内存空间

这也就是为什么可迭代对象创建的专属迭代器不会消耗太多的内存空间原因了。

2:迭代器对象一定属于可迭代对象范畴,所以在这里我们为迭代器对象linearContainer_iterator类也新增了__iter__()方法

这样做的好处在于如果单独的拎出了这个迭代器对象,则它也会支持for循环的遍历:

    def __iter__(self):  # 2return selfcontainerIterator = linearTypeContainer([i for i in range(10)]).__iter__()for item in containerIterator:print(item)# 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9

如果取消了linearContainer_iterator类的这个__iter__()方法,则不支持for循环的遍历:

...# def __iter__(self):  # 2#     return selfcontainerIterator = linearTypeContainer([i for i in range(10)]).__iter__()for item in containerIterator:print(item)# TypeError: 'linearContainer_iterator' object is not iterable

非线性可迭代对象与迭代器实现

如果是一个非线性容器的可迭代对象,可以先判断它的类型,如果传入的容器是一个字典,则将迭代的数据项集合转换为元组,里面存储的全部是字典的key即可。

如果传入的容器是一个集合,则将迭代的数据项集合转换为元组,再参照线性可迭代对象与迭代器的实现。

具体实现:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:531509025
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class mappingTypeContainer:def __init__(self, mapping):self.mapping = mappingself.mappingType = Noneif isinstance(mapping, dict):self.mappingType = "dict"elif isinstance(mapping, set) or isinstance(mapping, frozenset):self.mappingType = "set"else:raise TypeError("argument mapping must is mapping container")def keys(self):if self.mappingType == "set":raise TypeError("instance mapping type is set, no have method keys")else:return self.mappingdef values(self):if self.mappingType == "set":raise TypeError("instance mapping type is set, no have method values")else:return self.mapping.values()def items(self):if self.mappingType == "set":raise TypeError("instance mapping type is set, no have method items")else:return self.mapping.items()def __str__(self):return str(self.mapping)def __iter__(self):return mappingContainer_iterator(tuple(self.mapping))class mappingContainer_iterator:def __init__(self, array):self.index = 0self.array = arrayself.len = len(self.array)def __next__(self):if self.index < self.len:retDataItem = self.array[self.index]self.index += 1return retDataItemelse:raise StopIterationdef __iter__(self):return selfcontainer = mappingTypeContainer({str("k") + str(i): str("v") + str(i) for i in range(3)})for item in container.items():print(item)print(container)# ('k0', 'v0')
# ('k1', 'v1')
# ('k2', 'v2')
# {'k0': 'v0', 'k1': 'v1', 'k2': 'v2'}container = mappingTypeContainer({i for i in range(3)})for item in container:print(item)print(container)# 0
# 1
# 2
# {0, 1, 2}

迭代器对象的特性

每一次for循环创建出的可迭代对象的专属迭代器都是一次性的,用完后就没用了:

# 1
containerIterator = linearTypeContainer([i for i in range(3)]).__iter__()for item in containerIterator:print(item)# 0
# 1
# 2for item in containerIterator:print(item)  # 2print("?")

1:直接拿出一个迭代器对象

2:在第2次循环中,迭代器对象中存储的索引值已经最大了,每次调用iter()都会抛出异常返回出来再被for处理,所以print()函数根本不会运行

迭代器对象并不存储可迭代对象中的真正迭代数据,而是仅存储长度和索引,所以内存的占用并不多:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:531509025
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class linearContainer_iterator:def __init__(self, array):self.index = 0  # 1self.array = array  # 2self.len = len(self.array) # 3...

1:占用额外的内存空间

2:引用对象,并不开辟内存

3:占用额外的内存空间

惰性求值与及早求值

迭代器对象中对于返回的数据项,是进行实时演算的,这种实时演算的特性求值方式被称为惰性求值,即你需要的时候我算出来后再给你:

def __next__(self):if self.index < self.len:retDataItem = self.array[self.index]self.index += 1return retDataItemelse:raise StopIteration

除开惰性求值,还有一种及早求值的方案,即使你要1个,我也把所有的都给你。

如Python2中的range()、map()、filter()、dict.items()、dict.keys()、dict.values(),它们均返回的是一个纯粹的列表,这样的设计是不合理的。

因为返回的列表会占用很大的内存空间,而Python3中则统一优化为惰性求值方案,即返回一个可迭代对象。
要命的问题

①:Python中的所有自带容器类型为何不自己设置成迭代器?

而是在for循环时实例出一个专属的迭代器?

直接在这些自带类型的底层实现__next__()方法不好吗?

这样岂不是更加减少了内存的消耗,少定义了类和实例化了类吗?

答:这真是一个要命的问题,这个问题我也想过很久,最后是在stackoverflow提问并且获得了良好的解答才记录下来的。

因为确实是可以实现的,如下所示,只需要在加上1处代码即可:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:531509025
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class linearTypeContainer:def __init__(self, array):if isinstance(array, list) or isinstance(array, tuple):self.array = arrayelse:raise TypeError("argument array must is linear container")self.index = 0self.len = len(self.array)def __iter__(self):return selfdef __next__(self):if self.index < self.len:retDataItem = self.array[self.index]self.index += 1return retDataItemelse:self.index = 0  # 1raise StopIterationcontainer = linearTypeContainer(list(range(5)))for item in container:print(item)for item in container:print(item)for item in container:print(item)

但是这样做在某种特殊情况下会出现问题:

container = linearTypeContainer(list(range(5)))for item in container:print(item)if container.index == 3:breakprint("*"*20)for item in container: print(item)# 0
# 1
# 2
# ********************
# 3
# 4

你会发现如果第一次for循环到了1半的时候退出,第二次for循环会接着根据第一次for循环进行继续。

能够解决一下吗?只需要加上一个标志位即可:

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:531509025
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
class linearTypeContainer:def __init__(self, array):if isinstance(array, list) or isinstance(array, tuple):self.array = arrayelse:raise TypeError("argument array must is linear container")self.index = 0self.len = len(self.array)self.iter = False # 1def __iter__(self):if self.iter: # 2self.index = 0self.iter = Truereturn selfdef __next__(self):if self.index < self.len:retDataItem = self.array[self.index]self.index += 1return retDataItemelse:self.index = 0raise StopIterationcontainer = linearTypeContainer(list(range(5)))for item in container:print(item)if container.index == 3:breakprint("*" * 20)for item in container:print(item)# 0
# 1
# 2
# ********************
# 0
# 1
# 2
# 3
# 4

1:判断是不是一次新的调用

2:如果是新的调用,则将index重新置为0即可

那么为何Python不这样设计呢?我们应该更多的考虑多线程的情况下,多个for循环使用同一个迭代器它是否是线程安全的,上面的示例中这个共享迭代器并不是线程安全的,此外它也不支持嵌套循环,如下所示,这样会造成无限循环:

container = linearTypeContainer(list(range(5)))for item in container:print(item)for j in container:print(j)

综上各个方面的考虑,Python将内置的数据类型,都设置了在for循环时返回专属迭代器的做法,这是非常好的设计,但是对于有些内置的对象,则是将它本身做成了迭代器,如文件对象。

②:Python2中返回的及早求值对象,就没有一点好处吗?真的是浪费内存百无一用?

答:也不是,你可以发现Python3中所有返回的及早求值对象,都不支持索引操作,但是Python2中返回的由于是列表,它能够支持索引操作,在某些极度极端的情况下这确实是个优势,但是Python3的惰性求值对象需要这种优势吗?你手动将它转换为list不香吗?这样提供给了你更多操作性的同时优化了内存占用,何乐而不为呢?

③:你能实现一个返回惰性求值的对象吗?

答:能啊!你看,我实现一个Range吧,其实就是传参位置和自带的不一样,但是它是线程安全的且支持嵌套循环的:

class Range:def __init__(self, stop, start=0, step=1):self.start = startself.stop = stopself.step = stepself.current = Nonedef __iter__(self):return Range_iterator(self.stop, self.start, self.step)class Range_iterator:def __init__(self, stop, start, step):self.start = startself.stop = stopself.step = stepself.current = self.startdef __next__(self):if self.current < self.stop:retDataItem = self.currentself.current += self.stepreturn retDataItemraise StopIterationfor i in Range(10):print(i)for j in Range(10):print(j)

Python基础教程:迭代器相关推荐

  1. python基础教程第三版电子版百度云-《python基础教程第三版》高清版PDF免费下载...

    下载地址1:http://t.cn/EGxO1sW Python基础教程 第3版Python简明教程书籍 Python编程从入门到实践 灵程序设计丛书 <python基础教程第三版>高清版 ...

  2. python基础教程pdf百度云-《Python基础教程(第3版)》PDF高清版

    <python基础教程第3版>PDF高清版  百度云资源,内涵教材源代码 百度云链接:百度云盘 提取码:7r7o 编辑推荐 久负盛名的Python入门经典,是非常优秀的基础教程,深入浅出, ...

  3. python基础教程书籍推荐-初学者python入门必看书籍推荐(上)

    随着最近Python的热度上涨,学习的人数也在增多,也因为Python这门语言的简短.易学.语言优美的优点,吸引了很多人来学习它.初学者想要入门,就必须先通过阅读,学习大量的理论知识对Python有一 ...

  4. python基础教程(第二版)

    开始学习python,根据Python基础教程,把里面相关的基础章节写成对应的.py文件 下面是github上的链接 python基础第1章基础 python基础第2章序列和元组 python基础第3 ...

  5. python基础教程第三版电子版-《python基础教程第三版》PDF高清完整版-免费下载...

    <python基础教程第3版>高清PDF下载地址:http://t.cn/EGxO1sW Python基础教程 第3版Python简明教程书籍 Python编程从入门到实践 灵程序设计丛书 ...

  6. python基础教程书籍-Python学习必看书籍_带你高效学习

    坚持就是胜利,祝你成功!!! 都说python是最好的语言. 1.<笨办法学Python 3> 入门强烈推荐 本书是一本Python入门书,适合对计算机了解不多,没有学过编程,但对编程感兴 ...

  7. python基础教程电子版-Python基础教程(第2版 修订版) pdf

    Python基础教程(第2版 修订版) 目录 D11章快速改造:基础知识1 1.1安装Python1 1.1.1Windows1 1.1.2Linux和UNIX3 1.1.3苹果机(Macintosh ...

  8. python基础教程-《Python基础教程(第3版)》PDF高清版

    <python基础教程第3版>PDF高清版  百度云资源,内涵教材源代码 百度云链接:百度云盘 提取码:7r7o 编辑推荐 久负盛名的Python入门经典,是非常优秀的基础教程,深入浅出, ...

  9. 什么是python基础教程-python基础教程之python是什么?概念解析

    Python,是一种面向对象的解释型计算机程序设计语言,由荷兰人Guido van Rossum于1989年发明,第一个公开发行版发行于1991年. Python是纯粹的自由软件, 源代码和解释器CP ...

  10. python基本代码教程-Python基础教程(第3版)

    Python基础教程(第3版) 第2版前言 第1版前言 引言 1 快速上手:基础知识 1.1 交互式解释器 1.2 算法是什么 1.3 数和表达式 十六进制.八进制和二进制 1.4 变量 1.5 语句 ...

最新文章

  1. 美国政府牵头6家公司开发新一代超算 与中国竞争
  2. linux 杀死t状态进程,Linux查杀stopped进程
  3. 选了combobox里的选项后没激发change事件_备胎测试:来测测你被备胎了没?
  4. ICCV2021 Oral-新任务!新数据集!康奈尔大学提出了类似VG但又不是VG的PVG任务
  5. windows10 搜索桌面搜索功能失效的解决
  6. hMailServer邮件服务器之安装
  7. Spring Session
  8. oracle里面的double,oracle建表语句double
  9. 龙果 mycat mysql_龙果学院Spring Boot源码解析视频教程完整未加密(价值599)
  10. Booth算法乘法器
  11. keepalived高可用
  12. python字符串字母怎么取出_Python需要从字符串中提取第一个和最后一个字母
  13. PMP|项目经理如何做好相关方管理?
  14. QTimer::singleShot使用
  15. 40vf什么意思_LED的基本术语VF
  16. VRChat简易教程4-使用VRC的接口实现物体的移动(VRC的action和trigger接口)
  17. 数字滤波器matlab仿真_模拟IC笔记(1)——PLL MATLAB建模与行为仿真
  18. 学无止境中的曙光----维伯英语
  19. Web.config详解+asp.net优化(1)
  20. 中科大日常交流英语期末考试话题汇总

热门文章

  1. DotNetCore跨平台~xUnit和测试报告
  2. Racket 6.7最新版本:提供对Android App的支持及改进的REPL等等
  3. 巧用 GitHub 创建自己的私人 Maven 仓库,及一些开发Library的建议
  4. Mozilla 构建系统(转)
  5. shell实现矩阵转置
  6. 苹果欲借免费纳米SIM卡技术控制行业标准
  7. session_start() 对 HTTP_REQUEST扩展/fsockopen函数 的影响
  8. 使用 MDT2008 轻量部署进行 Windows XP SP2 简单部署
  9. 宏碁e5572g57mx加固态_宏基e5572g57mx怎么拆机
  10. 【MM模块】 Goods Issue 发货