Python中的迭代器和可迭代对象的概念混淆

4
我目前正在阅读Python 3.5的官方文档。文档指出range()可迭代,而list()for是迭代器。【4.3节】 然而,在这里指出,zip()会生成一个迭代器。
我的问题是,当我们使用这个指令时:
list(zip(list1, list2))

我们是否在使用一个迭代器(list())来遍历另一个迭代器?

for 不是一个迭代器。range() 是一个序列,就像 list 一样,两者都是可迭代的。你需要为可迭代对象创建一个新的迭代器来进行实际的迭代。 - Martijn Pieters
迭代器这个词实际上有两种不同的含义。我建议不要纠结于这些单词的确切含义。 - Alex Hall
我认为这里的混淆在于消费者(驱动迭代)和生产者(可用作)。 - Martijn Pieters
@MartijnPieters,你的第二条评论对我来说不太清楚。 - Ammar Alyousfi
@ammarx:抱歉,关于迭代器、可迭代对象以及它们的消费者,有时候可能会显得有些复杂。 - Martijn Pieters
显示剩余2条评论
2个回答

4

文档在这里造成了一些混淆,因为它重新使用了“iterator”一词。

迭代器协议有三个组成部分:可迭代对象、迭代器和消费者。

  1. 可迭代对象:您可以逐个获取其元素的事物。

  2. 迭代器:用于迭代的事物。每次想要遍历可迭代对象中所有项目时,都需要一个迭代器来跟踪您在过程中的位置。这些不可重复使用;一旦到达末尾,就是这样。对于大多数可迭代对象,您可以创建多个独立的迭代器,每个迭代器都可以单独跟踪位置。

  3. 迭代器的使用者:那些想要对项目执行操作的事物。

循环语句for是第3种情况的例子。循环语句for使用iter()函数为您要循环的任何内容生成一个迭代器(上面的#2),因此“任何内容”必须是可迭代对象(上面的#1)。

range()是第1种情况的例子;它是可迭代对象。您可以多次独立地迭代它。

>>> r = range(5)
>>> r_iter_1 = iter(r)
>>> next(r_iter_1)
0
>>> next(r_iter_1)
1
>>> r_iter_2 = iter(r)
>>> next(r_iter_2)
0
>>> next(r_iter_1)
2

这里的r_iter_1r_iter_2是两个不同的迭代器,每次请求下一个元素时,它们都会根据自己的内部记录提供答案。 list()是同时作为可迭代对象(#1)和迭代消费者(#3)的一个示例。如果你向list()传递另一个可迭代对象(#1),那么将生成包含该可迭代对象中所有元素的列表对象。但是列表对象本身也是可迭代的。
在Python 3中,zip()接受多个可迭代对象(#1),并且它本身是一个迭代器(#2)。zip()会为你提供的每个可迭代对象存储一个新的迭代器(#2)。每次询问zip()下一项时,它都会使用来自每个包含的可迭代对象的下一个元素构建一个新元组:
>>> lst1, lst2 = ['foo', 'bar'], [42, 81]
>>> zipit = zip(lst1, lst2)
>>> next(zipit)
('foo', 42)
>>> next(zipit)
('bar', 81)

最终,list(zip(list1, list2)) 使用了 list1list2 作为可迭代对象 (#1),zip() 消耗了这些对象 (#3),当它自己被外部的 list() 调用消耗时。


这该死的,你是怎么做到的?;-) - Zero Piraeus
@Zero:通过飞机前往另一个国家,我能够利用短暂的网络连接突击进入工作状态。;-) - Martijn Pieters
从你的回答和@ZeroPiraeus的回答中,我可以看出文档有时使用术语“迭代器”来指代“迭代器”的技术实际含义,而其他时候则是指迭代器的消费者。 - Ammar Alyousfi
但是为什么range()不被认为是一个iterator而是iterable呢?它难道不是像zip()一样工作的吗? - Ammar Alyousfi
2
不,它是一个虚拟序列。它有长度,包含测试有效,您可以比较两个范围并且可以反转它。您无法使用迭代器完成这些操作。 - Martijn Pieters
显示剩余2条评论

1
文档措辞不当。这是你所指的部分:
我们称这样的对象为可迭代对象,即适用于期望从中获取连续项直到供应耗尽的函数和结构的目标。我们已经看到for语句是这样一个迭代器。函数list()也是一个迭代器;它从可迭代对象创建列表。
在这一段中,iterator并不是指Python迭代器对象,而是“迭代某物”的一般概念。特别地,for语句不能是迭代器对象,因为它根本不是对象,而是一种语言结构。
回答你具体的问题:

... when we use this instruction:

list(zip(list1, list2))

are we using an iterator (list()) to iterate through another iterator?

不,list() 不是迭代器。它是 list 类型的构造函数。它可以接受任何可迭代对象(包括迭代器)作为参数,并使用该可迭代对象构造一个列表。

zip() 是一个迭代器函数,即返回迭代器的函数。在您的示例中,它返回的迭代器被传递给 list(),后者从中构造了一个 list 对象。

判断一个对象是否为迭代器的简单方法是使用 next() 函数调用它,并观察发生了什么:

>>> list1 = [1, 2, 3]
>>> list2 = [4, 5, 6]

>>> zipped = zip(list1, list2)
>>> zipped
<zip object at 0x7f27d9899688>
>>> next(zipped)
(1, 4)

在这种情况下,返回zipped的下一个元素。
>>> list3 = list(zipped)
>>> list3
[(2, 5), (3, 6)]

请注意,迭代器中仅找到最后两个元素在list3中,因为我们已经使用next()消耗了第一个元素。
>>> next(list3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'list' object is not an iterator

这段代码无法正常运行,因为列表不是迭代器。
>>> next(zipped)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

这一次,尽管 zipped 是一个迭代器,但是调用 next() 会引发 StopIteration 错误,因为它已经被用完来构建 list3


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接