对象变化时的Python迭代器
我想了解一下,当一个对象在迭代过程中发生变化时,迭代器一般会有什么样的表现。
以一个简单的可变列表为例,这点似乎很明显:迭代器会尝试继续获取下一个元素,如果没有了,就会返回StopIteration
,表示结束。
>>> l = range(10)
>>> a = iter(l)
>>> a.next()
0
>>> a.next()
1
>>> a.next()
2
>>> l[3]='a'
>>> a.next()
'a'
>>> a.next()
4
>>> del l[5]
>>> a.next()
6
>>> a.next()
7
>>> a.next()
8
>>> a.next()
9
>>> a.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
到这里为止,这些都很好理解。但我不明白的是,如果我在迭代过程中添加了一个新元素,迭代器仍然会返回StopIteration
。
>>> l.append(11)
>>> a.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
如果我在到达结束之前做同样的事情:
>>> l=[1]
>>> a=iter(l)
>>> a.next()
1
>>> l.append(2)
>>> a.next()
2
那么这背后是怎么运作的?对于更复杂的可变可迭代对象,预期的行为又是什么呢?(比如,想象一个表示图的对象,然后可以使用遍历算法进行迭代。如果在迭代时添加或删除节点,会发生什么?)
3 个回答
有一些迭代器,即使在抛出StopIteration
之后仍然可以继续提供数据;这样的迭代器被称为损坏的
。
这并不是说迭代器本身有问题——而是说,如果你不小心使用这样的迭代器,可能会导致程序出错或者代码出现问题。
在遍历一个对象时,改变这个对象通常会出现三种典型的情况:
- 会返回新的数据
- 新的数据会被忽略
- 旧的数据会被跳过
换句话说:实际的行为是不可预测的。
在遍历对象时修改它的内容是一个非常常见的问题,因此在Python 3中,像set
和dict
这样的类型(可能还有其他类型)被修改为在遍历时如果检测到添加或删除操作,就会立即抛出错误。
在关于这个问题的讨论中,有人提到了PEP 234(迭代器):
如果一个迭代器对象已经抛出了StopIteration(停止迭代),那么在后续的next()调用中,它还会继续抛出StopIteration吗?
有些人认为应该强制要求这样做,这样会更有用;而另一些人则认为让每个迭代器自己决定更好。需要注意的是,这可能会让某些迭代器的实现需要额外的状态标志(比如,函数包装的迭代器)。
解决方案是:一旦抛出了StopIteration,调用它的next()方法会继续抛出StopIteration。
注意:实际上在Python 2.2中并没有实现这个功能;在很多情况下,迭代器的next()方法在一次调用中可能会抛出StopIteration,但在下一次调用中却不会。这在Python 2.3中得到了修复。