【问题标题】:Python strange behavior with enumerate枚举的Python奇怪行为
【发布时间】:2017-07-28 00:38:37
【问题描述】:

我知道我不应该在循环中修改列表,但出于好奇,我想知道为什么以下两个示例的迭代次数不同。

示例 1:

x = [1, 2, 3, 4, 5]
for i, s in enumerate(x):
    del x[0]
    print(i, s, x)

示例 2:

x = [1,2,3,4,5]
for i, s in enumerate(x):
    x = [1]
    print(i, s, x)

示例 1 只运行了 3 次,因为当 i==3len(x)==2 时。

示例 2 运行 5 次,即使 len(x)==1

所以我的问题是,enumerate 是否会在循环开始时生成 (index, value) 对的完整列表并遍历它?或者它们是在循环的每次迭代中生成的?

【问题讨论】:

  • 我不能假装我知道的足以回答你的问题,但至于为什么它的行为不同,这是我的猜测。在第一种情况下,您将从同一个列表中删除,因此迭代之前停止是有意义的。但是,在第二种情况下,您正在重新分配它。所以python可能认为它是一个different变量并继续使用x的original值。
  • 完全没有枚举也会发生同样的情况! for 循环不会重新评估迭代器,因此即使您在 for 循环内重新分配 x,循环仍将使用旧值。显然,如果您从列表中删除元素,循环将以更少的迭代完成。
  • 这一定是骗人的。
  • enumerate 需要适用于未绑定的序列,因此无法预先生成对。如果你想,只需使用list(enumerate(...))
  • @jpmc26 确实有 stackoverflow.com/a/986145/3451198 我昨天没有找到。它确实很好地解释了潜在问题,但不是在循环的上下文中。这应该被标记吗?我是 SO 的新手,不确定。

标签: python list loops iterator enumerate


【解决方案1】:
x = [1, 2, 3, 4, 5]

列表[1, 2, 3, 4, 5]x“标记”

for i, s in enumerate(x):

enumerate() 附加了另一个标签,因此[1, 2, 3, 4, 5] 现在被标记为xy。 enumerate() 将继续使用y 标签,而不是x 标签。

del x[0]

存储在内存中的列表被修改,所以xy现在都引用[2, 3, 4, 5]

或者,当您使用时

x = [1]

在内存中创建了一个新列表[1]x 标记现在指向它。 y 标记仍然指向原始列表。

Python 变量的工作原理:
http://foobarnbaz.com/2012/07/08/understanding-python-variables/

【讨论】:

    【解决方案2】:

    其他人已经指出,您的第二个示例仅更改了 x 指向的值,而不是您正在迭代的列表。这是普通赋值 (x = [1]) 和 slice assignment (x[:] = [1]) 之间区别的完美示例。后者修改列表x指向就地

    x = [1, 2, 3, 4, 5]
    for i, s in enumerate(x):
        x[:] = [1]
        print(i, s, x)
    

    将打印

    (0, 1, [1])
    

    【讨论】:

      【解决方案3】:

      确实:您的第一个 sn-p 修改了迭代列表;第二个将变量x 指向一个新列表,使enumerate() 横向的列表保持不变。您可以通过访问 www.pythontutor.com 上的以下链接来查看实际情况,这使您可以单步执行代码并可视化变量的内容:

      为了更好地了解发生了什么,请转到here,而不是跳过以下扩展代码:

      x = [1,2,3,4,5]
      view = enumerate(x)
      for i, s in view:
          x = [1]
          print(i, s, x)
      

      【讨论】:

        【解决方案4】:

        enumerate() 返回一个迭代器,或其他支持迭代的对象。 enumerate() 返回的迭代器的 __next__() 方法返回一个元组,其中包含一个计数(从 start 默认为 0)和通过迭代 iterable 获得的值。

        __next__() 从容器中返回下一项。如果没有其他项目,请引发 StopIteration 异常。

        enumerate() 是否在循环开始时生成 (index, value) 对的完整列表并遍历它?或者它们是在循环的每次迭代中生成的?

        所以,enumerate() 返回一个迭代器,并且在每次迭代时,__next__() 检查是否还有其他项目。 enumerate() 不会在循环开始时创建完整列表。

        正如@Wisperwind 所提到的,在您的第二种情况下,您将一个新对象分配给名称x。循环迭代的对象在迭代期间不会改变。

        【讨论】:

        • 所以原 x 的值 [1, 2,..., 5] 即使在分配 x=[0] 后也没有被垃圾回收,是因为迭代器仍然引用这个列表.
        【解决方案5】:

        只是对 Wasi Ahmad 和 Wisperwind 所说的话的澄清。两者都声明“您只是将一个新对象分配给名称 x”。这可能有点令人困惑,因为它可能被解释为“您正在创建一个新对象 ([1]) 并将其存储到名称 x,您会说“好吧,那为什么不它改变了?!”要查看发生了什么,请打印出对象的 id

        x = [1, 2, 3, 4, 5]
        y = x  # To keep a reference to the original list
        print id(x), id(y)
        for i, v in enumerate(x):
            x = [1]
            print id(x), id(y)
        print id(x), id(y)
        
        
        # output (somewhat contrived as I don't have a python environment set up)
        #    X ID            Y ID
        10000000000001 10000000000001
        10000000000002 10000000000001
        10000000000003 10000000000001
        10000000000004 10000000000001
        10000000000005 10000000000001
        10000000000006 10000000000001
        10000000000006 10000000000001
        

        您会注意到xid 每次循环都会发生变化,当您完成循环时,x 将指向循环中所做的最后修改。当您通过循环时,它会遍历 x 的原始实例,无论您是否仍然可以引用它。

        如您所见,y 指向原来的x。当您在循环中进行迭代时,即使 x 发生变化,y 仍指向原始的 x,而该 x 仍在循环中。

        【讨论】:

        • Python 在很大程度上是一种基于参考的语言。您没有为名称 x 赋值,而是为名称 x 分配了一个引用。
        • 当我运行此代码时,循环内x 的 ID 在 139917134004304 和 139917134053248 之间交替。这是因为新对象与最后一个对象创建在同一位置.但是,这种皱纹会使您的答案更加复杂!
        • 另外,如果 OP 想要减少 x 引用的 original 列表(并且他正在循环),他可以写:del x[1:] : x[0] = 1跨度>
        • @MartinBonner 甚至:x[:] = [1]
        【解决方案6】:

        在第一个示例中,您实际上是在修改您正在迭代的列表。

        另一方面,在第二种情况下,您只是将一个新对象分配给名称x。但是,循环迭代的对象并没有改变。

        请查看http://foobarnbaz.com/2012/07/08/understanding-python-variables/,了解有关 Python 中名称和变量的更详细说明。

        【讨论】:

        • 感谢您的回答!我选择 Wasi 的答案只是因为使用 __next__() 调用更容易理解。
        • @dbdq 我认为这与 Python 变量的工作方式有关,这可能就是为什么这个答案有更多的支持。
        • @dbdq 我认为我的回答很清楚这里发生了什么
        猜你喜欢
        • 2012-11-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多