【问题标题】:How does Python iterate a for loop?Python 如何迭代一个 for 循环?
【发布时间】:2012-08-02 20:46:46
【问题描述】:

我在 Python 上尝试了以下代码,结果如下: 似乎对于我尝试通过更改 elem 对可迭代对象进行的许多更改,它不起作用。

lis = [1,2,3,4,5]
for elem in lis:
    elem = 3

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

但是,如果可迭代对象是具有自己方法的对象(如列表),则可以在 for 循环中对其进行修改。

lis = [[1],[2]]
for elem in lis:
    elem.append(8)

print lis
    [[1, 8], [2, 8]]

for 循环中,'elem' 术语到底是什么?提前致谢!

【问题讨论】:

标签: python loops for-loop


【解决方案1】:

这不起作用的原因是因为您误解了elem 。不是对象本身,甚至称它为“变量”也不正确。

这是一个名称,有点像标签,指向对象。如果你只是直接分配它,你只是覆盖这个名字指向别的东西。但是,您仍然拥有对列表的原始引用,因此为 elem 分配不同的值不会修改 lis 本身。

现在,在这种情况下,由于 elem 指向的所有对象都是整数,您甚至根本无法更改它们 - 因为整数(以及许多其他类型,如字符串或元组)是不可变的。简而言之,这意味着一旦创建了对象,就无法对其进行修改。这与它们是否“有方法”无关(所有 Python 对象都有方法,包括整数),而是它们是否不可变。

但是,有些对象是可变的,这意味着它们可以更改。列表是此类对象的示例。在您的第二个示例中,elem 是一个引用 lis 中包含的 list 对象的名称,这些对象本身是可变的。这就是为什么就地修改它们(使用.append().remove() 等)可以正常工作的原因。

【讨论】:

  • 但是,您仍然拥有对列表的原始引用,因此不会对其进行修改。 注意:for elem in lis: lis = 3 仍然有效,即使您更改了原始引用引用该列表,因为首先计算了 for in ... 表达式。
  • @jamylak:是的,但是在循环结束后,lis 将被破坏。由于您没有更多指向列表对象的引用,它将被垃圾收集器吞噬。
  • 我的问题是这部分不是很清楚 imo。仅仅通过对列表的原始引用并不意味着您不能修改它。例如。您可以改变原始引用本身。整句话是什么意思?
  • @jamylak:很抱歉,但这对我来说并不模棱两可。它没有说“你不能修改那个”,它说“不会修改那个”,例如“elem分配不同值的行为 不会修改它。”
  • @jamylak:我对其进行了修改以使其听起来更明确。
【解决方案2】:

for 循环中的elem 变量是每次迭代时对当前对象的引用。改变它不会做任何事情;它只会更改变量elem 的值,并且无论如何都会在下一次通过循环时更改。要真正改变列表中元素的值,你需要对列表的引用和要改变的元素的索引,而后者是没有的。

所以你想做的是这样的:

for index, elem in enumerate(lis):
    lis[index] = 3

这样你就有elem 用于元素的值,index 用于列表中的位置。它使您无需不断编写lis[index] 来获取值,但您仍然必须这样做才能更改 元素。

你也可以这样做:

for index in xrange(len(lis)):
    lis[index] = 3

但是,在大多数情况下,这被认为是非 Pythonic(除其他外,如果列表在迭代时变得更长或更短会发生什么情况)?

【讨论】:

    【解决方案3】:

    在这里,您实际上是在修改第二个示例中的列表对象。在第一个示例中,您不是在修改数字,而是在替换它。对于 Python 的新用户来说,这可能是一个复杂的细微差别。

    看看这个:

    >>> x = 1
    >>> id(x)
    4351668456
    >>> x = 2
    >>> id(x)
    4351668432
    

    id 返回对象的标识符。正如您在上面看到的,x 的对象在这两次都发生了变化。

    >>> y = [1]
    >>> id(y)
    4353094216
    >>> y.append(2)
    >>> id(y)
    4353094216
    

    这里,我修改了列表,所以列表还是原来的对象y

    所以,这一切意味着当你在做elem = 3 时,它不是在修改它,而是在替换它。现在,它不再与列表关联了。

    这是您可以做您想做的事情的方法之一。这会抓取索引,然后修改列表,而不是数字。

    lis = [1,2,3,4,5]
    for idx, elem in enumerate(lis):
        lis[idx] = 3
    
    print lis
    [1, 2, 3, 4, 5]
    

    【讨论】:

      【解决方案4】:

      当您为名称elem 分配一个新值时,您只需更改for 循环中的本地绑定。如果要更改存储在lis 中的值,请使用maplist comprehension,如下所示:

      lis = [3 for elem in lis]
      

      但是,您可以修改 elem 的属性(或调用这样做的方法),就像您可以修改任何其他值一样。

      【讨论】:

        【解决方案5】:

        在您的第一个示例中,您尝试修改一个整数,它是不可变的(就像字符串一样)。

        Python 变量应被视为指向对象的标签。当您遍历一个不可变列表时,elem 指向一个不可变对象,而不是列表中的那个位置,因此您无法修改原始列表。

        在第二种情况下,elem 指向一个可以修改的对象,因此您会看到原始列表已更改。

        【讨论】:

          【解决方案6】:

          这取决于elem的type()是什么。

          在您的第一种情况下,每个 elem 都是一个 int 对象,并且可以更改它。当您说:elem = 3,而不是列表中的项目本身时,您正在更改临时对象。

          在第二种情况下,每个元素都是一个列表对象。

          【讨论】:

          • 不,但在他的第一种情况下确实有效。我不明白他想为列表分配一个值。他想知道为什么elem = 3 没有就地修改列表。
          • 您说过元素的类型将表示您是否可以执行elem = 3,这两种方式都不起作用。实际区别在于,第二种情况会改变对象,而不是将名称 elem 绑定到本地命名空间中的 3。