前言
Python 中的 for 循环和其他语言中的 for 循环工作方式是不一样的,今天就带你深入了解 Python 的 for 循环,看看它是如何工作的,以及它为什么按照这种方式工作。
循环中的陷阱
我们先来看一下 Python 循环中的「陷阱」,在我们了解了循环的工作方式后,再来看下这些陷阱到底是怎么出现的。
陷阱 1:循环两次
现在我们先假设有一个数字组成的列表,和一个用于返回这些数字的平方的生成器:
> nums = [1, 2, 3, 4] > squares = (n**2 for n in nums)
我们可以将这个生成器对象传递给元组构造器,从而可以得到一个元组:
> tuple(squares) (1, 4, 9, 16)
这个时候,如果我们再将这个构造器对象传递给 sum 函数,按理说应该会返回这些数字的和吧:
> sum(squares) 0
返回的是个 0,先拖住下巴。
陷阱 2:检查是否包含
我们还是使用上面的数字列表和生成器:
> nums = [1, 2, 3, 4] > squares = (n**2 for n in nums)
如果我 squares 生成器中是否包含 9,答案是肯定的,若果我再问一次呢?
你敢答应吗
> 9 in squares True > 9 in squares False
发现,第二次不灵了~
怎么不灵了
陷阱 3:拆包
现在假设有一个字典:
> counts = {1:'a', 2:'b'}
然后,我们用多个变量对字典进行拆包:
> x,y = counts
你觉得这时候,x 和 y 中会是什么?
> x 1 > y 2
我们只得到了键。
下面,我们先来了解下 Python 中的循环工作原理,然后再反过头来看这些陷阱问题。
一些概念
首先,先了解一些基本概念:
可迭代和序列
可迭代就是指任意可以使用 for 循环遍历的东西,可迭代意味着可以遍历,任何可以遍历的东西都是可迭代的。
for item in some_iterable: print(item)
序列是一种常见的可迭代类型,如列表、元组、字符串等。
序列是可迭代的,它有着一些特点,它们是从 0 开始索引,索引长度不超过序列的长度;它们有序列长度;并且它们可以被切分。
Python 中的大部分东西都是可以迭代的,但是可以迭代并不意味着它是序列。如集合、字典、文件和生成器都是可迭代的,但是它们都不是序列。
> my_set = {1, 2, 3} > my_dict = {'k1': 'v1', 'k2': 'v2'} > my_file = open('some_file.txt') > squares = (n**2 for n in my_set)
总结下来就是,任何可以用 for 循环遍历的东西都是可迭代的,序列可迭代的类型中的一种,Python 还有着许多其他种类的可迭代类型。
迭代器
迭代器就是可以驱动可迭代对象的东西。你可以从任何可迭代对象中获得迭代器,你也可以使用迭代器来手动对它的迭代进行遍历。
下面有三个可迭代对象:一个集合、一个元祖和一个字符串:
> nums = {1,2,3,4} > coors = (4,5,6) > words = "hello hoxis"
我们可以使用 Python 的内置函数 iter ,从这些可迭代对象中获取到迭代器:
> iter(nums) <setiterator object at 0x7fa8c194ad70> > iter(coors) <tupleiterator object at 0x7fa8c1959610> > iter(words) <iterator object at 0x7fa8c19595d0>
一旦我们有了迭代器,我们就可以使用其内置函数 next() 来获取它的下一个值:
> nums = {1,2,3,4} > num_iter = iter(nums) > next(num_iter) 1 > next(num_iter) 2 > next(num_iter) 3 > next(num_iter) 4 > next(num_iter) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
若果迭代到头了,也就是没有下一个值了,就会抛出 StopIteration 异常。也就是说,它不会继续循环取获取第一个值。
是不是有点懵逼了?
- 可迭代对象是可以迭代的东西
- 迭代对象器实际上是遍历可迭代对象的代理
- 迭代器没有长度,它们不能被索引。
- 可以使用迭代器来做的唯一有用的事情是将其传递给内置的 next 函数,或者对其进行循环遍历
- 可以使用 list() 函数将迭代器转换为列表
> nums = {1,2,3,4} > num_iter = iter(nums) > next(num_iter) 1 > list(num_iter) [2, 3, 4] > list(num_iter) []
若果想再次将其转换为列表,明显地,得到的是一个空列表。
其实这也是迭代器的一个重要特性:惰性,只能使用一次,只能循环遍历一次。并且,在我们调用 next() 函数之前,它不会做任何事情。因此,我们可以创建无限长的迭代器,而创建无限长的列表则不行,那样会耗尽你的内存!
可迭代对象不一定是迭代器,但是迭代器一定是可迭代的:
对象
可迭代?
迭代器?
可迭代对象
√
不一定
迭代器
√
√
生成器
√
√
列表
√
×
其实,Python 中有许多迭代器,生成器是迭代器,Python 的许多内置类型也是迭代器。例如,Python 的 enumerate 和 reversed 对象就是迭代器。zip, map 和 filter 也是迭代器;文件对象也是迭代器。
Python 中的 for 循环
其实,Python 并没有传统的 for 循环,什么是传统的 for 循环?
我们看下 Java 中的 for 循环:
int[] integers = {1, 2, 3, 4}; for (int j = 0; j<integers.length; j++) { int i = integers[j]; System.out.println(i); }
这是一种 C风格 的 for 循环,JavaScript、C、C++、Java、PHP 和一大堆其他编程语言都有这种风格的 for 循环,但是 Python 确实没有。
Python 中的我们称之为 for 循环的东西,确切的说应该是 foreach 循环:
numbers = [1, 2, 3, 5, 7] for n in numbers: print(n)
和 C风格 的 for 循环不同之处在于,Python 的 for 循环没有索引变量,没有索引变量的初始化,边界检查和索引变量的增长。
这就是 Python 的 for 循环的不同之处!
使用索引?
你可能会怀疑,Python 的 for 循环是否在底层使用了索引,下面我们手动的使用 while 循环和索引来遍历:
> nums = [1,2,3,4] > i = 0 > while i < len(nums): ... print(num[i]) ... i += 1 ... 0 1 2 3
对于列表,这样遍历是可以的,但不代表适用于所有可迭代对象,它只适用于序列。
比如,我们对一个 set 使用这种方法遍历,会得到一个异常:
> set = {1,2,3} > i = 0 > while i < len(set): ... print(set[i]) ... i += 1 ... Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: 'set' object does not support indexing
因为 set 不是序列,因此不支持索引遍历。
我们不能使用索引手动对 Python 中的每一个迭代对象进行遍历。对于那些不是序列的迭代器来说,更是行不通的。
实现没有 for 的循环
从上文可以看出,Python 中的 for 循环不使用索引,它使用的是迭代器。让我们来看下它是如何工作的。
通过上文,我们了解到了迭代器和 iter、next 函数,现在我们可以尝试不用 for 循环来遍历一个可迭代对象。
下面是一个正常的 for 循环:
def funky_for_loop(iterable, action_to_do): for item in iterable: action_to_do(item)
我们要尝试用迭代器的方法和 while 实现上面 for 循环的逻辑,大致步骤如下:
- 获取给定可迭代对象的迭代器;
- 调用迭代器的 next() 方法获取下一项;
- 对当前项数据进行处理;
- 如果捕获到 StopIteration ,那么就停止循环
def funky_for_loop(iterable, action_to_do): iterator = iter(iterable) while not done_looping: try: item = next(iterator) except StopIteration: break else: action_to_do(item)
Python 底层的循环工作方式基本上如上代码,就是迭代器驱动的 for 循环。
再次回到循环陷阱
陷阱 1:耗尽的迭代器
陷阱 1 中,因为生成器是迭代器,迭代器是惰性的,也是一次性的,在已经遍历过一次的情况下,再对其求和,返回的就是一个 0。
陷阱 2:部分消耗迭代器
陷阱 2 中,我们两次询问 9 是否存在于同一个生成器中,得到了不同的答案。
这是因为,第一次询问时,Python 已经对这个生成器进行了遍历,也就是调用 next() 函数查找 9,找到后就会返回 True,第二次再询问 9 是否存在时,会从上次的位置继续 next() 查找。
> nums = [1,2,3,4,5] > squares = (n**2 for n in nums) > 9 in squares True # 此时打印出来 > list(squares) [16, 25]
陷阱 3:拆包是迭代
当直接在字典上迭代时,得到的是键:
> counts = {1:'a',2:'b'} > for i in counts: ... print(i) ... 1 2
而对字典拆包时,和在字典上遍历是一样的,都是依赖于迭代器协议,因此得到的也是键。
总结
序列是迭代器,但是不是所有的迭代器都是序列。迭代器不可以被循环遍历两次、不能访问其长度,也不能使用索引。
迭代器是 Python 中最基本的可迭代形式。如果你想在代码中做一个惰性迭代,请考虑迭代器,并考虑使用生成器函数或生成器表达式。
最后,请记住,Python 中的每一种迭代都依赖于迭代器协议,因此理解迭代器协议是理解 Python 中的循环的关键。
原文链接:https://opensource.com/article/18/3/loop-better-deeper-look-iteration-python
好了以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新动态
- DzintraErliha-Dreamscape(2024)[24Bit-96kHz]FLAC
- 群星.1999-上华世纪国语总冠军4CD【上华】【WAV+CUE】
- 叶丽仪.1992-仙鹤神针电视剧原声音乐专辑【星传真】【WAV+CUE】
- 陈柏宇.2011-QUINQUENNIUM【SONY】【WAV+CUE】
- 《51首热门当红歌曲 十大顶级发烧女声 3CD》[WAV+CUE][1.6GB]
- 《赵传 深受摇滚迷青睐的台湾音乐人 约定》[WAV+CUE][320MB]
- 《庄心妍 实力唱将 发烧音乐精选集》[WAV+CUE][380MB]
- [音展纪念碟]2006年《TAA台湾第十六届国际HI-ENDHI-FI音响展》2cd[WAV]
- 中国音乐地图之听见西藏山歌牧歌酒歌2020[WAV分轨]
- 王宏伟2017-永远的红飘带[太平洋影音][WAV+CUE]
- 费翔.1997-NOW现在【第一中国】【WAV+CUE】
- 新格合唱团.1981-我唱你和2辑(滚石新格民歌系列)【新格】【WAV+CUE】
- 陈柏宇.2011-QUINQUENNIUM【SONY】【WAV+CUE】
- 雨果唱片-《金唛系列-蔷薇依韵琼瑶》WAV
- 陈百强《凝望(2012华纳LPCD45限量版)[DMI]》[WAV整轨]