为什么从原始列表中弹出会使 reversed(original_list) 为空?

she*_*per 33 python python-3.x

我有以下代码:

s = [1,2,3]
t = reversed(s)

for i in t:
    print(i)
# output: 3,2,1
Run Code Online (Sandbox Code Playgroud)

如果我从s(原始)中弹出一个元素,那么t(反向)将被清空:

s = [1,2,3]
t = reversed(s)
s.pop()

for i in t:
    print(i)
# expected output: 2, 1
# actual output (nothing): 
Run Code Online (Sandbox Code Playgroud)

为什么会发生这种情况?

M Z*_*M Z 30

看看GitHub上的cpython代码,我们可以直观地了解为什么它不再起作用。

返回的迭代器本质上需要知道最后一个索引的位置和数组的长度。如果数组的大小发生变化,迭代器将不再工作。

测试 1:增加数组长度

这也不会产生正确的结果,但迭代器确实运行:

s = [1,2,3]
t = reversed(s)
s.append(4)

for i in t:
    print(i)
# output: [3, 2, 1]
Run Code Online (Sandbox Code Playgroud)

测试 2:减少,然后增加长度

s = [1,2,3]
t = reversed(s)
s.pop()
s.append(4)

for i in t:
    print(i)
# output: [4, 2, 1]
Run Code Online (Sandbox Code Playgroud)

它仍然有效!

所以有一个内部检查来查看最后一个索引是否仍然有效,如果是,它是一个简单的 for 循环到索引 0。

如果它不起作用,迭代器返回空。

  • 检查位于“第 3339 行”:“if (index>=0 && index < PyList_GET_SIZE(seq))”,其中“seq”是指向原始“list”对象的指针。如果“PyList_GET_SIZE”检查失败,则删除引用并返回“NULL”。 (2认同)

Cop*_*eld 9

在该列表上调用reversed返回一个迭代器,它是一个特殊的对象,允许您在原始列表上以相反的顺序迭代,它不是一个新列表,只能一次性使用

>>> s= [1,2,3]
>>> t = reversed(s)
>>> t
<list_reverseiterator object at 0x00000261BE8F0C40>
>>> list(t)
[3, 2, 1]
>>> list(t)
[]
>>> 
Run Code Online (Sandbox Code Playgroud)

并且因为这个迭代器引用了原始列表,所以当您稍后迭代迭代器时,它的任何更改都会反映出来。

更新

特别是正如 MZ 所解释的那样,如果这种变化使得列表的状态与创建迭代器时的状态不同,如果大小减小,您将一无所获,或者如果增加则列表的不完整版本

>>> s= [1,2,3]
>>> t = reversed(s)
>>> s.insert(0,23)
>>> s
[23, 1, 2, 3]
>>> list(t)
[2, 1, 23]
>>> t = reversed(s)
>>> s.append(32)
>>> list(t)
[3, 2, 1, 23]
>>> s
[23, 1, 2, 3, 32]
>>> t = reversed(s)
>>> s.pop()
32
>>> list(t)
[]
>>> 
Run Code Online (Sandbox Code Playgroud)

  • @Deadbeef 不过,迭代器由原始列表支持,因此更改列表的状态会影响迭代器,因为它在更改后不一定有效。在大多数语言中,在使用迭代器模式迭代集合时更改集合通常是一个坏主意。 (3认同)