python 反向迭代器与生成器行为
python reverse iterator vs. generator behavior
为什么删除列表中的项目会破坏 reversed
个对象?它不会破坏 gen-exprs,追加或修改列表不会破坏 reversed
对象,而且它显然指向原始对象,那么为什么不能给出截断版本?也许一些例子可以说明:
l = [1, 2, 3, 4]
r = reversed(l)
g = (i for i in l)
l.pop() # returns 4
l # returns [1, 2, 3]
for i in g:print(i) # prints 1 2 3 (on separate lines)
for i in r:print(i) # prints ...nothing
r = reverse(l)
g = (i for i in l)
l[1] = 4
for i in g:print(i) # prints 1 4 3 (on separate lines)
for i in r:print(i) # prints 3 4 1 (on separate lines)
r = reversed(l)
g = (i for i in l)
l.append(5)
l # returns [1, 4, 3, 5] just to keep you on your toes
for i in g:print(i) # prints 1 4 3 5 (on separate lines)
for i in r:print(i) # prints 3 4 1 (on separate lines)
所以 - 如果 genexpr 足够聪明,可以指向对象,并且只响应对象的变化,为什么 reversed
不呢?它显然不会复制,否则它不会在第一种情况下 "fail" ,并且在第二种情况下不会选择 4
。所以它必须指向对象。为什么它不能从索引 -1
开始并向后工作?
当您在列表对象上调用 reversed()
时,会创建一个专用的反向列表迭代器对象;此对象 'knows' 如何以相反的顺序有效地遍历列表 once.
为此,创建对象时,存储列表中的最后一个索引。对于您的列表 l
,即 3
(第 4 个元素,从 0 开始计数)。然后进行迭代时,会生成该索引处的元素,并且索引会递减,直到引发 IndexError
*.
对象的 Python 实现如下所示:
class reversed_list_iterator(object):
def __init__(self, lst):
self.index = len(lst) - 1
self.lst = lst
def __iter__(self):
return self
def __next__(self):
try:
result = self.lst[self.index]
except IndexError:
self.lst = [] # switch to permanently stopped state
raise StopIteration
self.index -= 1
return result
现在,当您 删除 该元素时,迭代器会退出,因为没有 l[3]
,引发 IndexError
然后 和迭代结束。
在您的第二个示例中,创建反向迭代器时,最后一个索引是 2
。然后您 添加 到列表,但迭代从 l[2]
开始,它仍然存在。
反向列表迭代器不能使用相对索引,因为正如您发现的那样,迭代器对添加到列表中的元素具有相对容忍度。相对索引将重复值。
* actual C implementation 测试边界 0 <= index < len(self.lst)
而不是 catch IndexError
,但原理是一样的。
为什么删除列表中的项目会破坏 reversed
个对象?它不会破坏 gen-exprs,追加或修改列表不会破坏 reversed
对象,而且它显然指向原始对象,那么为什么不能给出截断版本?也许一些例子可以说明:
l = [1, 2, 3, 4]
r = reversed(l)
g = (i for i in l)
l.pop() # returns 4
l # returns [1, 2, 3]
for i in g:print(i) # prints 1 2 3 (on separate lines)
for i in r:print(i) # prints ...nothing
r = reverse(l)
g = (i for i in l)
l[1] = 4
for i in g:print(i) # prints 1 4 3 (on separate lines)
for i in r:print(i) # prints 3 4 1 (on separate lines)
r = reversed(l)
g = (i for i in l)
l.append(5)
l # returns [1, 4, 3, 5] just to keep you on your toes
for i in g:print(i) # prints 1 4 3 5 (on separate lines)
for i in r:print(i) # prints 3 4 1 (on separate lines)
所以 - 如果 genexpr 足够聪明,可以指向对象,并且只响应对象的变化,为什么 reversed
不呢?它显然不会复制,否则它不会在第一种情况下 "fail" ,并且在第二种情况下不会选择 4
。所以它必须指向对象。为什么它不能从索引 -1
开始并向后工作?
当您在列表对象上调用 reversed()
时,会创建一个专用的反向列表迭代器对象;此对象 'knows' 如何以相反的顺序有效地遍历列表 once.
为此,创建对象时,存储列表中的最后一个索引。对于您的列表 l
,即 3
(第 4 个元素,从 0 开始计数)。然后进行迭代时,会生成该索引处的元素,并且索引会递减,直到引发 IndexError
*.
对象的 Python 实现如下所示:
class reversed_list_iterator(object):
def __init__(self, lst):
self.index = len(lst) - 1
self.lst = lst
def __iter__(self):
return self
def __next__(self):
try:
result = self.lst[self.index]
except IndexError:
self.lst = [] # switch to permanently stopped state
raise StopIteration
self.index -= 1
return result
现在,当您 删除 该元素时,迭代器会退出,因为没有 l[3]
,引发 IndexError
然后 和迭代结束。
在您的第二个示例中,创建反向迭代器时,最后一个索引是 2
。然后您 添加 到列表,但迭代从 l[2]
开始,它仍然存在。
反向列表迭代器不能使用相对索引,因为正如您发现的那样,迭代器对添加到列表中的元素具有相对容忍度。相对索引将重复值。
* actual C implementation 测试边界 0 <= index < len(self.lst)
而不是 catch IndexError
,但原理是一样的。