为什么迭代器第二次不起作用?
它是“工作的”,在示例中的for
循环确实运行。 只是执行了零次迭代。这是因为迭代器已经“耗尽”了; 它已经迭代完所有元素。
为什么其他类型的可迭代对象可以工作?
因为在幕后,每个循环都基于该可迭代对象创建了一个新的迭代器。从头开始创建迭代器意味着它从开头开始。
这是因为迭代需要可迭代对象。如果已经提供了可迭代对象,则将其按原样使用;否则,需要进行转换,从而创建一个新对象。
给定一个迭代器,如何两次迭代数据?
通过缓存数据;使用新迭代器重新开始(假设我们可以重新创建初始条件);或者,如果迭代器专门设计用于此目的,则寻找或重置迭代器。相对较少的迭代器提供寻找或重置功能。
缓存
唯一完全通用的方法是记住第一次看到了哪些元素(或确定将要看到哪些元素),然后再次迭代它们。最简单的方法是通过从迭代器创建一个列表或元组:
elements = list(iterator)
for element in elements:
...
for element in elements:
...
由于列表是非迭代器可迭代对象,每个循环都会创建一个新的可迭代对象,用于遍历所有元素。如果在此之前迭代器已经“部分完成”了一个迭代过程,则该列表仅包含“后续”的元素:
abstract = (x for x in range(10))
next(abstract)
concrete = list(abstract)
for element in concrete:
print(element)
for element in concrete:
print(element)
一种更复杂的方法是使用
itertools.tee
。它基本上创建了一个“缓冲区”,在迭代原始源时从中获取元素,然后创建并返回多个自定义迭代器,这些迭代器通过记住索引、在可能的情况下从缓冲区获取数据,并在必要时(使用原始可迭代对象)将数据添加到缓冲区中来工作。(在现代 Python 版本的参考实现中,这不使用本地 Python 代码。)
from itertools import tee
concrete = list(range(10))
x, y = tee(concrete, 2)
for element in x:
print(element)
if element == 3:
break
for element in y:
print(element)
重新开始
如果我们知道并能重新创建迭代器启动时的起始条件,那么这也解决了问题。当多次在列表上进行迭代时,这就是隐含发生的事情:“迭代器的起始条件”只是列表的内容,从中创建的所有迭代器都会给出相同的结果。例如,如果生成器函数不依赖于外部状态,我们可以简单地使用相同的参数再次调用它:
def powers_of(base, *range_args):
for i in range(*range_args):
yield base ** i
exhaustible = powers_of(2, 1, 12):
for value in exhaustible:
print(value)
print('exhausted')
for value in exhaustible:
print(value)
print('replenished')
for value in powers_of(2, 1, 12):
print(value)
可寻址或可重置的迭代器
一些特定的迭代器可能会使得重置迭代到开头,甚至可以“寻址”到迭代中的特定位置成为可能。一般来说,迭代器需要具有某种内部状态以便跟踪它们在迭代中的“位置”。使迭代器“可寻址”或“可重置”只是意味着允许外部访问相应的状态进行修改或重新初始化。
Python 中没有任何禁止这样做的规定,但在许多情况下提供简单的接口是不可行的;在大多数其他情况下,即使这很容易,也不支持此操作。对于生成器函数,相应的内部状态非常复杂,并且保护自己免受修改。
可寻址迭代器的经典示例是使用内置的
open
函数创建的
打开的文件对象。所谓的状态是指磁盘上底层文件中的位置;
.tell
和
.seek
方法允许我们检查和修改该位置的值——例如,
.seek(0)
将把位置设置为文件的开头,有效地重置迭代器。同样,
csv.reader
是一个文件的包装器;在该文件中进行搜索将影响迭代的后续结果。
在除了最简单、经过特别设计的情况下,迭代器倒回可能会非常困难,甚至不可能。即使迭代器被设计为可寻址,这也留下了一个问题:如何确定要寻址到哪里 - 即,在迭代中所需点的内部状态是什么。对于上面展示的
powers_of
生成器来说,这很简单:只需修改
i
。对于文件来说,我们需要知道所需行的开头的
文件位置,而不仅仅是行号。这就是为什么文件接口提供了
.tell
和
.seek
的原因。
下面是一个重新设计的
powers_of
示例,表示一个无限序列,并设计为可寻址、可倒回和可通过
exponent
属性重置:
class PowersOf:
def __init__(self, base):
self._exponent = 0
self._base = base
def __iter__(self):
return self
def __next__(self):
result = self._base ** self._exponent
self._exponent += 1
return result
@property
def exponent(self):
return self._exponent
@exponent.setter
def exponent(self, value):
if not isinstance(new_value, int):
raise TypeError("must set with an integer")
if new_value < 0:
raise ValueError("can't set to negative value")
self._exponent = new_value
例子:
pot = PowersOf(2)
for i in pot:
if i > 1000:
break
print(i)
pot.exponent = 5
print(next(pot))
print(next(pot))
技术细节
迭代器 vs. 可迭代对象
回想一下,简单来说:
"iteration" 意味着逐一查看某个抽象的、概念上的值序列中的每个元素。这可以包括:
"iterable" 意味着表示这样一个序列的对象。(Python 文档所称的“序列”实际上比这更具体——基本上还需要是有限的和有序的。)请注意,元素不需要被“存储”——在内存、磁盘或任何其他地方;我们可以在迭代过程中确定它们即可。
"iterator" 意味着表示
迭代过程的对象;在某种意义上,它跟踪“我们在迭代中的位置”。
将两个定义合并,可迭代对象是指代表可以按照特定顺序检查的元素;而迭代器是允许我们按照特定顺序检查元素的东西。当然,迭代器“代表”这些元素——因为我们可以通过检查它们来找出它们是什么——而且它们可以按照特定顺序检查——因为这就是迭代器所能实现的。因此,我们可以得出结论:
迭代器是可迭代对象的一种,Python的定义也是如此。
迭代需要一个迭代器。在Python中进行迭代时,需要使用迭代器;但在正常情况下(即除了用户编写不良代码之外),任何可迭代对象都是允许的。在幕后,Python会将其他可迭代对象转换为相应的迭代器;这方面的逻辑可通过
内置的iter
函数获得。要进行迭代,Python会反复向迭代器请求“下一个元素”,直到迭代器引发
StopException
异常。这方面的逻辑可通过
内置的next
函数获得。
通常情况下,当给
iter
传递一个已经是迭代器的单一参数时,它将原样返回同一对象。但如果是其他类型的可迭代对象,将创建一个新的迭代器对象。这直接导致了OP中的问题。用户定义的类型可能会违反这两个规则,但最好不要这样做。
迭代器协议
Python粗略地定义了一个"迭代器协议",指定了它如何确定一个类型是否为可迭代(或具体为迭代器),以及类型如何提供迭代功能。细节在过去的几年中发生了一些变化,但现代设置的工作方式如下:
任何具有 __iter__
方法或 __getitem__
方法的对象都是可迭代的。任何定义了 __iter__
方法和 __next__
方法的对象都是迭代器。 (特别注意,如果存在 __getitem__
和 __next__
但不存在 __iter__
,那么 __next__
就没有特定的意义,该对象就是一个非迭代器可迭代对象。)
如果给出单个参数,iter
将尝试调用该参数的 __iter__
方法,验证其结果具有 __next__
方法,并返回该结果。它不会确保结果上存在 __iter__
方法。这种对象通常可以在期望迭代器的位置使用,但如果对它们调用 iter
,则会失败。如果没有 __iter__
,则会查找 __getitem__
,并使用它来创建内置迭代器类型的实例。该迭代器大致等同于
class Iterator:
def __init__(self, bound_getitem):
self._index = 0
self._bound_getitem = bound_getitem
def __iter__(self):
return self
def __next__(self):
try:
result = self._bound_getitem(self._index)
except IndexError:
raise StopIteration
self._index += 1
return result
给定一个参数,
next
将尝试调用该参数的
__next__
方法,允许任何
StopIteration
传播。
有了这些机制,可以通过
while
实现
for
循环。具体来说,像下面这样的循环:
for element in iterable:
...
将近的意思是:
iterator = iter(iterable)
while True:
try:
element = next(iterator)
except StopIteration:
break
...
除了迭代器实际上没有被赋予任何名称外(这里的语法强调了即使没有迭代“...”代码也会调用一次iter)。
def _view(self,dbName): db = self.dictDatabases[dbName] data = db[3]
部分,因为没有其他答案讨论该代码部分。 - Mateen Ulhaq