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

Confusion about iterators and iterables in Python

我目前正在阅读 Python 3.5.

的官方文档

它指出 range() 是可迭代的,并且 list()for 是迭代器。 [section 4.3]

但是,here 它指出 zip() 生成了一个迭代器。

我的问题是,当我们使用这条指令时:

list(zip(list1, list2))

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

文档在这里通过重新使用术语 'iterator' 造成了一些混乱。

iterator protocol 包含三个组成部分:

  1. 可迭代对象;您可以可能迭代并逐一获取它们的元素的东西。

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

  3. 迭代器的消费者;那些想对物品做点什么的人。

A for 循环是后者的一个例子,所以#3。 for 循环使用 iter() function 为您想要循环的任何内容生成一个 迭代器 (上面的#2),因此 "whatever" 必须是一个可迭代的(上面的#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) 的示例。如果将另一个可迭代对象 (#1) 传递给 list() 调用,则会生成一个列表对象,其中包含该可迭代对象中的所有元素。但是列表对象本身也是可迭代的。

zip(),在 Python 3 中,接收多个可迭代对象 (#1),并且它本身就是一个迭代器 (#2)。 zip() 为您提供的每个可迭代对象存储一个新的迭代器 (#2)。每次你向 zip() 询问下一个元素时,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() 在它本身是被外部 list() 调用消耗。

文档措辞不当。这是您所指的部分:

We say such an object is iterable, that is, suitable as a target for functions and constructs that expect something from which they can obtain successive items until the supply is exhausted. We have seen that the for statement is such an iterator. The function list() is another; it creates lists from iterables:

在本段中,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()是一个iterator function,即函数里面returns一个iterator。在您的示例中,迭代器 it returns 被传递给 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.