19 | 深入理解迭代器和生成器
景霄
该思维导图由 AI 生成,仅供参考
你好,我是景霄。
在第一次接触 Python 的时候,你可能写过类似 for i in [2, 3, 5, 7, 11, 13]: print(i) 这样的语句。for in 语句理解起来很直观形象,比起 C++ 和 java 早期的 for (int i = 0; i < n; i ++) printf("%d\n", a[i]) 这样的语句,不知道简洁清晰到哪里去了。
但是,你想过 Python 在处理 for in 语句的时候,具体发生了什么吗?什么样的对象可以被 for in 来枚举呢?
这一节课,我们深入到 Python 的容器类型实现底层去走走,了解一种叫做迭代器和生成器的东西。
你肯定用过的容器、可迭代对象和迭代器
容器这个概念非常好理解。我们说过,在 Python 中一切皆对象,对象的抽象就是类,而对象的集合就是容器。
列表(list: [0, 1, 2]),元组(tuple: (0, 1, 2)),字典(dict: {0:0, 1:1, 2:2}),集合(set: set([0, 1, 2]))都是容器。对于容器,你可以很直观地想象成多个元素在一起的单元;而不同容器的区别,正是在于内部数据结构的实现方法。然后,你就可以针对不同场景,选择不同时间和空间复杂度的容器。
所有的容器都是可迭代的(iterable)。这里的迭代,和枚举不完全一样。迭代可以想象成是你去买苹果,卖家并不告诉你他有多少库存。这样,每次你都需要告诉卖家,你要一个苹果,然后卖家采取行为:要么给你拿一个苹果;要么告诉你,苹果已经卖完了。你并不需要知道,卖家在仓库是怎么摆放苹果的。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
本文深入探讨了Python中的迭代器和生成器的概念及应用。首先介绍了容器、可迭代对象和迭代器的概念,强调了迭代器提供了next方法,可以逐个获取容器中的元素。接着详细介绍了生成器的概念,将其描述为懒人版本的迭代器,并通过内存占用和计算资源的比较展示了生成器的优势。文章还通过一个自定义生成器的例子展示了生成器的灵活性和无限集合的特点。最后,通过一个常规问题的解决方法和生成器的对比,突出了生成器的便捷性和高效性。整体内容生动有趣,深入浅出地介绍了迭代器和生成器的概念及应用,适合读者快速了解和掌握相关知识。文章还总结了容器、可迭代对象、迭代器和生成器的关系,以及生成器在Python 2和Python 3.5版本中的不同应用。最后留下了一个思考题,引发读者思考和讨论。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Python 核心技术与实战》,新⼈⾸单¥59
《Python 核心技术与实战》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(102)
- 最新
- 精选
- Jingxiao置顶思考题答案: 很多同学的回复非常正确,生成器只能遍历一次,继续调用 next() 会 raise StopIteration。只有复位生成器才能重新进行遍历。2019-06-23551
- John Si置顶我不知道如何把这技巧运用在编程中,老师能否举几个例子来说明一下呢?谢谢
作者回复: 例子已经在文中举了不少,对于如何娴熟地在编程中运用,这个需要长时间的积累,从阅读别人高质量的源代码,自己主动有意识地在自己的项目中思考,最后才会形成质变,内化成自己的能力,从而清楚地知道哪里应该用高级语法,高级工具,哪里应该简单的一笔带过。Python 的生成器无疑是最有用的特性,但也是最不广泛被使用的特性,这一章的目的,能够让你对生成器有基本的了解,下次在代码中遇到,能够说,“这个我知道,这个我懂!”便已足够。加油!
2019-06-21340 - TKbookdef is_subsequence(a, b): b = iter(b) print(b) gen = (i for i in a) print(gen) for i in gen: print(i) gen = ((i in b) for i in a) print(gen) for i in gen: print(i) return all(((i in b) for i in a)) print(is_subsequence([1, 3, 5], [1, 2, 3, 4, 5])) print(is_subsequence([1, 4, 3], [1, 2, 3, 4, 5])) ########## 输出 ########## <list_iterator object at 0x000001E7063D0E80> <generator object is_subsequence.<locals>.<genexpr> at 0x000001E70651C570> 1 3 5 <generator object is_subsequence.<locals>.<genexpr> at 0x000001E70651C5E8> True True True False <list_iterator object at 0x000001E7063D0D30> <generator object is_subsequence.<locals>.<genexpr> at 0x000001E70651C5E8> 1 4 3 <generator object is_subsequence.<locals>.<genexpr> at 0x000001E70651C570> True True False False 为什么这里的print(is_subsequence([1, 3, 5], [1, 2, 3, 4, 5]))会返回False? 解释一下: 因为 gen = ((i in b) for i in a) print(gen) for i in gen: print(i) 这段代码的 for i in gen 已经b的迭代器消耗完,此时的b已经是个空的迭代器。所以,再执行all(((i in b) for i in a)),就是False了。
作者回复: 解释的很好
2019-06-21654 - SCAR思考题:对于一个有限元素的生成器,如果迭代完成后,继续调用 next(),会跳出StopIteration:。生成器可以遍历多次吗?不行。也正是这个原因,老师代码复杂化那段代码,在 gen = ((i in b) for i in a) for i in gen: print(i) 之后应该是需要给b复位下,不然b会是空的,那么return回来永远会是False。 这段判断是否是子序列的指针用的真是巧妙,区区几行,精华尽现。
作者回复: 👍
2019-06-2131 - 恨你有错误的地方:list_1 = [i for i in range(100000000)]结果是一个可迭代对象,是一个列表,而不是一个迭代器。所以文中使用例子来说明的是生成器比列表节省内存,而不是迭代器比生成器节省内存。 from collections import Iterator from sys import getsizeof a = [i for i in range(1001)] print(type(a)) print(getsizeof(a))#4516 b = iter(a) print(type(b)) print(isinstance(b,Iterator)) print(getsizeof(b))#32 c = (i for i in range(1001)) print(getsizeof(b)) #32 这个例子可以说明生成器跟迭代器一样,都可以节省内存。请详细讲明可迭代对象,迭代器生成器的关系,重点说明迭代器与生成器的关系,生成器是特殊的迭代器,特殊之处不在于生成器能够节省内存。具体哪里特殊,个人还没有好的总结,只是浅显认为:生成器写法更优雅,可以使用send方法修改值 ,请老师深入研究下后讲给我们。谢谢。
编辑回复: 收到,我看看
2019-11-15820 - tt明白为啥要把b转换成迭代器了,是为了下面的代码中可以用next(): while True: val = next(b) if val == i: yield True 这样才可以利用next()可以保存指针位置的特性,从而确保子序列中元素的顺序。
作者回复: 对,这里是个很巧妙的利用
2019-06-21215 - farFlight迭代完成后,继续调用 next()会出现StopIteration。 生成器只能遍历一次,但是可以重新调用重新遍历。
作者回复: 正确
2019-06-2110 - Lonely绿豆蛙比较下return 与 yield的区别: return:在程序函数中返回某个值,返回之后函数不在继续执行,彻底结束。 yield: 带有yield的函数是一个迭代器,函数返回某个值时,会停留在某个位置,返回函数值后,会在前面停留的位置继续执行,直到程序结束。
作者回复: 正确
2020-02-0528 - 力维最后的例子的确比较有意思,个人理解有三个关键点: 一是(i in b)的含义 b = (i for i in range(5)) print(2 in b) print(4 in b) # 执行完后,b中剩下5了 print(3 in b) # 此时3 不在b中 二是,gen = ((i in b) for i in a) 先执行 for i in a ,把a中元素逐个取出;再执行 i in b ,判断是否在b中;最后把判断结果保存在生成器gen中 三是,return all(((i in b) for i in a)) 由于之前 i in b使得b到达StopIteration,再执行就是空集了 另外,第七段“集合(set: set([0, 1, 2]))都是容器。” 是否改为 set: {0,1,2}比较好?虽然都是同一个意思。
作者回复: 理解的很好。 大括号是很好很简洁的 set 初始化写法,这里用 set() 是为了更容易让读者明白具体类型。
2020-02-193 - Geek_59f23e1、大家对next函数可能有些误区,迭代完成后继续调用next函数会返回默认值None。 iterator.__next__() 方法和 next(iterator, default=None) 函数的区别在于:前者迭代完成后会抛出StopIteration错误,中断程序运行,而后者会返回一个默认值None(可以指定),不会报错和中断程序运行。 2、生成器遍历到最后一个元素后抛出StopIteration,不能遍历多次,重新遍历需要生成一个新的生成器。
作者回复: 👍
2019-06-223
收起评论