Python中不允许修改列表迭代器?

8

简单的例子:

myList = [1, 2, 3, 4, 5]
for obj in myList:
  obj += 1
print myList

打印

[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]

while:

myList = [1, 2, 3, 4, 5]
for index in range(0,len(myList)):
  myList[index] += 1
print myList

打印
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]

结论:

  1. 可以使用全局列表访问来就地修改列表
  2. 不能使用迭代器对象就地修改列表项

我能找到的所有示例代码都使用全局列表访问器来就地修改列表。修改列表迭代器是否邪恶?


这段代码会产生一个 NameError,因为 a 没有被定义。 - senderle
你的代码示例很糟糕。前两个打印输出来自哪里?你在打印不存在的变量 'a',并且有两个输出。 - Matt Alcock
有点相关 - Lauritz V. Thaulow
抱歉大家,“a” 明显应该是 “myList”。在帖子中已经修正。 - RobW
6个回答

7
obj += 1语句之所以不能按预期运行,是因为此语句并未在原位置修改obj。相反,它计算出新值,并将变量obj重新绑定到该新值。这意味着列表的内容保持不变。
通常情况下,使用for obj in myList迭代列表时,确实有可能修改列表。例如:
myList = [[1], [2], [3], [4], [5]]
for obj in myList:
  obj[0] += 1
print(myList)

这将打印出以下内容:
[[2], [3], [4], [5], [6]]

这个例子与你的第一个例子不同之处在于,这里的列表包含了 可变 对象,并且代码会就地修改这些对象。
请注意,你也可以使用列表推导式来编写循环:
myList = [val+1 for val in myList]

谢谢你的回答。我已经得出结论,obj迭代器不像C++中的迭代器那样,修改对象并不会改变列表。你所说的是,obj再次绑定到下一次迭代中将丢失的新对象上。明白了! - RobW
很棒的回答!这与被接受的答案所讨论的“浅拷贝”无关。 - user2558887

7

我认为你误解了“迭代器对象”的含义。一个for循环不是一个迭代器对象。就所有实际目的而言,像这样的for循环:

myList = [0, 1, 2, 3, 4]
for x in myList:
    print x

这个功能做同样的事情(但更高效且不那么冗长):
i = 0
while i < len(myList)
    x = myList[i]
    print x
    i += 1

因此,您可以看到,对x进行的任何更改都会在下一个循环开始时丢失,因为x的值被列表中下一个项目的值覆盖。

正如其他人所观察到的那样,在迭代列表时更改列表的值是可能的。(但不要改变列表的长度!这是会导致问题的地方。)一种优雅的方法如下:

for i, x in enumerate(myList):
    myList[i] = some_func(x)
更新: 在for循环中,没有复制的操作。在上面的例子中,ix -- 像Python中的所有变量一样 -- 更像是C/C++中的指针。随着for循环的进行,obj依次指向myList[0]myList[1]等。就像C/C++指针一样,指向的对象的属性在指针改变时不会改变。但是像C指针一样,你可以直接修改所指向的东西,因为它不是一个副本。在C中,这是通过对指针进行解引用来完成的;在Python中,这是通过使用可变对象来完成的。这就是为什么NPE的答案有效的原因。如果ix甚至是浅拷贝,他所做的事情就不可能实现。
不能像更改list那样直接更改int的原因(如NPE的答案所示),是因为int是不可变的。一旦创建了一个5对象,就无法更改它的值。这就是为什么在Python中传递指向5的指针是安全的 -- 不会发生副作用,因为所指向的东西是不可变的。

显然,print a 应该是 print myList,我已经修复了这个问题。感谢指出 enumerate,非常有用,避免了自己维护列表索引的代码! - RobW
为了更加Pythonic和简洁,你可以使用[some_func(x) for x in myList]或者map(some_func, myList),虽然这些都会复制初始列表。 - Ryan Madsen

6
for obj in myList: 中,每次迭代中的 objmyList 中元素的(浅)复制。因此对 obj 的更改不会影响 myList 的元素。
这与 Perl 的 for my $obj (@myList) {} 不同。

我明白了。obj不像在C++中那样是一个迭代器,它基本上只是对象的副本,在下一次迭代中被忽略。因此,只能使用全局/直接列表访问来进行修改。 - RobW
1
@RobW,obj不是一个副本,这个答案是误导性的。像Python中的所有变量一样,obj更像是C/C++中的指针。请参见我的答案的编辑。 - senderle
Ade YU,我了解您所说的“copy”,指的是“元素地址的副本”而不是“元素本身的副本”。您可以澄清一下。 - senderle
@senderle 是的。在Python中,变量只是对对象的引用。只有变量被复制,而不是对象。就像obj = myList[index]发生在每次迭代的开始一样。所以obj += 1不会影响myList[index]。谢谢。我会改进我的答案陈述。 - Ade YU
如果一个列表是嵌套的,那么它可能不是一个浅拷贝。 - Ninja420

1

你感到困惑了。考虑一下你的第一个片段:

myList = [1, 2, 3, 4, 5]
for obj in myList:
  obj += 1
print a

obj 不是指向列表的某种神奇指针。它是一个变量,它持有一个对象的引用,该对象恰好也在 myList 中。obj += 1 的效果是增加 obj 中存储的值。然后,您的代码对该值什么都没有做。

明确一点:此代码示例中没有副本。obj 是一个变量,它持有列表中的一个对象。就这样。


如果 obj 是像 C++ 中的迭代器一样,那么列表对象将被更新。但显然,obj 是一个副本,在每次迭代后更改都会被忽略。显然,Python 执行后者,这是我的问题所在。 - RobW
@RobW:跟我重复一遍:这段代码示例中没有任何副本。obj是一个变量,它保存了列表中的一个对象。就是这样。 - Marcin

0

列表中的修改是允许的。你上面的代码示例相当混乱...

myList = [1, 2, 3, 4, 5]
for index in range(0,len(myList)):
   myList[index] += 1
 print myList

这个有效。


这确实是我发布的内容,但并没有回答任何问题。 - RobW

0
在第一个例子中,整数被复制到 obj 中,然后增加 1。列表不会改变。
如果您使用类实例并对其执行操作,则会更改它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接