python中的for循环在底层是如何开展工作的?( 五 )

我们从可迭代对象中手动获取一个迭代器,在它上面调用 next 来获取第一项,然后循环遍历迭代器获取后续所有的项目,跟踪后一个项目 。这个函数不仅适用于序列,而且适用于任何类型迭代 。
这段代码和以前代码是一样的,但是我们使用的是辅助函数而不是手动跟踪 next_item:
differences = []for current, next_item in with_next(readings):differences.append(next_item - current)请注意,这段代码不会挂在我们循环周围的 next_item 上,with_next 生成器函数处理跟踪 next_item 的工作 。
还要注意,这段代码已足够紧凑,如果我们愿意,我们甚至可以将方法复制到列表推导中来 。
differences = [(next_item - current)for current, next_item in with_next(readings)]再次回顾循环问题
现在我们准备回到之前看到的那些奇怪的例子并试着找出到底发生了什么 。
问题 1:耗尽的迭代器
这里我们有一个生成器对象 squares:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)如果我们把这个生成器传递给 tuple 构造函数,我们将会得到它的一个元组:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)>>> tuple(squares)(1, 4, 9, 25, 49)如果我们试着计算这个生成器中数字的和,使用 sum,我们就会得到 0:
>>> sum(squares)0这个生成器现在是空的:我们已经把它耗尽了 。如果我们试着再次创建一个元组,我们会得到一个空元组:
>>> tuple(squares)()生成器是迭代器,迭代器是一次性的 。它们就像 Hello Kitty Pez 分配器那样不能重新加载 。
问题 2:部分消耗一个迭代器
再次使用那个生成器对象 squares:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)如果我们询问 9 是否在 squares 生成器中,我们会得到 True:
>>> 9 in squaresTrue但是我们再次询问相同的问题,我们会得到 False:
>>> 9 in squaresFalse当我们询问 9 是否在迭代器中时,Python 必须对这个生成器进行循环遍历来找到 9 。如果我们在检查了 9 之后继续循环遍历,我们只会得到最后两个数字,因为我们已经在找到 9 之前消耗了这些数字:
>>> numbers = [1, 2, 3, 5, 7]>>> squares = (n**2 for n in numbers)>>> 9 in squaresTrue>>> list(squares)[25, 49]询问迭代器中是否包含某些东西将会部分地消耗迭代器 。如果没有循环遍历迭代器,那么是没有办法知道某个东西是否在迭代器中 。
问题 3:拆包是迭代
当你在字典上循环时,你会得到键:
>>> counts = {'apples': 2, 'oranges': 1}>>> for key in counts:... print(key)...applesoranges当你对一个字典进行拆包时,你也会得到键:
>>> x, y = counts>>> x, y('apples', 'oranges')循环依赖于迭代器协议,可迭代对象拆包也依赖于有迭代器协议 。拆包一个字典与在字典上循环遍历是一样的,两者都使用迭代器协议,所以在这两种情况下都得到相同的结果 。
回顾
序列是迭代器,但是不是所有的迭代器都是序列 。当有人说“迭代器”这个词时,你只能假设他们的意思是“你可以迭代的东西” 。不要假设迭代器可以被循环遍历两次、询问它们的长度或者索引 。
迭代器是 Python 中最基本的可迭代形式 。如果你想在代码中做一个惰性迭代,请考虑迭代器,并考虑使用生成器函数或生成器表达式 。
最后,请记住,Python 中的每一种迭代都依赖于迭代器协议,因此理解迭代器协议是理解 Python 中的循环的关键 。




推荐阅读