只要您愿意依靠Python解释器的多个未记录内部,就可以做到您想做的事情(在我的情况下,是CPython 3.7),但这并没有什么好处。
迭代器不会暴露给locals
或其他任何地方(甚至不会暴露给调试器)。但正如Patrick Haugh所指出的那样,您可以通过get_referrers
间接访问它。例如:
for ref in gc.get_referrers(seq):
if isinstance(ref, collections.abc.Iterator):
break
else:
raise RuntimeError('Oops')
当然,如果您有两个不同的迭代器指向同一个列表,我不知道是否有任何方法可以在它们之间进行选择,但让我们忽略这个问题。
现在,你该怎么办呢?你已经有了一个迭代器用于seq,那么接下来呢?你不能将其替换为有用的东西,比如
itertools.chain(seq, [1, 2, 3])
。没有公共API来改变列表、集合等迭代器,更不用说任意迭代器了。
如果你碰巧知道它是一个列表迭代器......好吧,CPython 3.x的
listiterator
确实是可变的。它们被pickle的方式是创建一个空迭代器,并调用
__setstate__
来引用一个列表和一个索引:
>>> print(ref.__reduce__())
(<function iter>, ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9],), 7)
>>> ref.__setstate__(3) # resets the iterator to index 3 instead of 7
>>> ref.__reduce__()[1][0].append(10) # adds another value
但这有点儿愚蠢,因为您可以通过改变原始列表来获得相同的效果。实际上:
>>> ref.__reduce__()[1][0] is seq
True
所以:
lst = list(range(10))
for elem in lst:
print(elem, end=' ')
if elem % 2:
lst.append(elem * 2)
print()
将会输出:
0 1 2 3 4 5 6 7 8 9 2 6 10 14 18
...无需与迭代器打交道。
使用set无法做到同样的事情。
当您在迭代集合时对其进行修改将影响迭代器,就像修改列表一样——但它的行为是不确定的。毕竟,集合具有任意顺序,只有在您不添加或删除元素时才能保证顺序一致。如果在中间添加或删除元素会发生什么?你可能会得到完全不同的顺序,这意味着你可能会重复已经迭代过的元素,并错过从未看到的元素。Python 暗示在任何实现中都应该是非法的,并且 CPython 实际上进行了检查:
s = set(range(10))
for elem in s:
print(elem, end=' ')
if elem % 2:
s.add(elem * 2)
print()
这将立即引发以下问题:
RuntimeError: Set changed size during iteration
那么,如果我们使用相同的技巧绕过Python,找到set_iterator
并尝试更改它会发生什么呢?
s = {1, 2, 3}
for elem in s:
print(elem)
for ref in gc.get_referrers(seq):
if isinstance(ref, collections.abc.Iterator):
break
else:
raise RuntimeError('Oops')
print(ref.__reduce__)
在这种情况下,你将看到类似于以下内容:
您将看到的是:
2
(<function iter>, ([1, 3],))
1
(<function iter>, ([3],))
3
(<function iter>, ([],))
换句话说,当你使用pickle来序列化一个set_iterator时,它会创建一个剩余元素的列表,并返回用于构建新列表迭代器的指令。对临时列表进行变异显然没有任何有用的效果。
那么元组呢?显然,你不能直接变异元组本身,因为元组是不可变的。但是迭代器呢?
在CPython中,tuple_iterator共享与listiterator相同的结构和代码(调用iter方法并定义__len__和__getitem__但未定义__iter__的“旧样式序列”类型也是如此)。因此,你可以使用完全相同的技巧来获取迭代器并缩小它。
但是一旦你这样做了,ref.__reduce__()[1][0] is seq就会再次成立——换句话说,它是一个元组,与你已经拥有的相同元组,仍然是不可变的。
__iter__
函数被执行"这并不完全正确。更准确地说,应该是iter(MyObject)
。对象可以在没有定义__iter__
方法的情况下成为可迭代对象(通过定义__getitem__
方法)。 - Aran-Feylistiterator
保留了列表的引用和索引的等效值。(它可能被优化为更有效的东西,比如指向列表内部存储的中间加上针对重新分配内存的保护,但它必须像持有一个列表和一个索引一样运作。) - abarnertdel lst
并不能删除这个列表,它只是删除了一个指向列表值的引用变量。如果还有其他引用(比如列表迭代器中的引用),那么这个列表就不是垃圾,因此也不会被删除。 - abarnert