【问题标题】:about iterators in python [duplicate]关于python中的迭代器[重复]
【发布时间】:2013-04-09 14:22:53
【问题描述】:

如果我有以下列表:

a = [1, 2, 3]

然后我运行以下代码:

for x in a:
  x += 1

看来这并没有改变列表a

但是,如果我执行以下操作:

for i in range(0, len(a)):
  a[i] += 1

这会修改'a'的内容。

所以我猜xa[i] 以不同的方式指代a 的元素。究竟是什么导致了这种差异?它们各自如何引用a 的元素?

【问题讨论】:

  • 值得注意的是,像这样按索引循环通常是一个非常糟糕的主意,如果您想修改列表的每个元素,请考虑使用list comprehension。 (这确实会创建一个新列表,但这很少会成为问题,如果需要,将修改推送回原始列表是微不足道的。)
  • This visualisation 也应该提供一些见解。
  • 投反对票,因为这个问题显然是重复的。
  • @Lattyware 您好,感谢您的评论!我想知道为什么这是一个非常糟糕的主意?这比列表理解慢吗?
  • @Enzo 它速度较慢,可读性较差,并且仅适用于列表,而不适用于通用迭代器(例如,这意味着您无法利用 Python 的惰性生成器)。一般来说,按索引迭代并不是解决 Python 问题的最佳方式。

标签: python loops iterator


【解决方案1】:

当您遍历一个列表时,每个元素都会依次生成。但是,有不同种类的对象。 可变不可变。当您执行以下操作时:

a += 1

对于不可变对象,它大致翻译为:

a = a + 1

现在在这种情况下,您通过a 获取对象引用,将其加1 以创建一个新对象。然后为该新对象分配名称a。请注意,如果我们在迭代时这样做,我们根本不会触及列表——我们只会不断创建新对象并将它们分配给名称a

这对于 可变 对象是不同的。然后a += 1 实际上更改了对象就地。因此,列表将看到更改,因为它所持有的对象已更改(突变)。 (对于不可变对象,列表中包含的对象没有改变,因为它不可能)。请参阅this question 了解更多信息。

这也使您在按索引迭代时更清楚地知道发生了什么。您构造了一个新整数并将其放入列表中(忘记之前该插槽中的任何内容)。

【讨论】:

  • +1,实际回答了问题,并提到了namesobjects是分开的关键点。
  • @Lattyware -- 是的。我仍然不肯定我喜欢名称/对象的语义,因为右侧的名称就像一个对象......无论你怎么想它都会变得浑浊。但是......这是一个棘手的问题,尽管之前有人问过,但我认为尝试解释它的人越多,我们就越有可能得到与特定用户产生共鸣的答案。
  • 诀窍是赋值是一种特殊情况。当您分配一个值时,您会说“这个名字现在意味着右边的东西”,而不是“这个东西变得等于右边的这个东西”,这是大多数人的想法。这就是为什么= 实际上是一个非常垃圾的赋值运算符(在某些方面 - 它使代码自然阅读,但给人以先入为主)。我个人一直是 : 的粉丝。
  • 当您意识到“名称”用于在本地或全局字典中查找对象时,它会变得更加清晰。然后分配给“名称”实际上只是在适当的字典中插入或替换键/值。
【解决方案2】:

当你说,

for x in [1,2,3]:
    x+=1

您是说,暂时将 x 保存为变量,然后在该临时保存中添加一个。当您进行下一次迭代时,垃圾人会破坏该变量,因为它是临时的。 x 不是列表中的位置。它是列表中该位置的值。

编辑:当我说它被删除时,我并不清楚我的话。发生的情况是,每次通过循环时,x 都会被另一个值替换,所以之前发生的事情就消失了(除非你用它做了其他事情)。但是,使用您正在使用的操作,您不会更改列表中任何元素的值。我对混乱不好。

当你以另一种方式做时,

for x in range(len(lst)):
    lst[x] += 1

那么您正在谈论列表的值。 x 是变量的索引,因此可以在该位置更改列表值的值。

【讨论】:

  • 我会小心地说它会被销毁。例如。 >>> for x in (1, 2, 3): x += 1>>> print x4(不过我知道你的意思)
【解决方案3】:

这里重要的概念是引用。在 Python 中,变量是对位于内存某处的对象的引用。让我们使用箭头 → 来表示引用。变量→对象。左边是变量,右边是对象。

数组可以被可视化为三个变量,引用三个整数对象。

a[0] → int(1)
a[1] → int(2)
a[2] → int(3)

现在,整数对象是不可变的。它们不能改变。当您更改整数变量时,您不会更改变量所引用的对象。你不能,因为ints 是不可变的。你可以做的是让变量引用一个不同的对象。

直接更新

让我们先看看第二个循环,因为它更简单。如果直接更新数组会发生什么?

for i in range(0, len(a)):
  a[i] += 1

首先让我们展开循环:

a[0] += 1
a[1] += 1
a[2] += 1

对于整数,a[0] += 1 等价于 a[0] = a[0] + 1。首先,Python 计算a[0] + 1 并得到结果int(2)。然后它将a[0] 更改为引用int(2)。第二个和第三个语句的评估类似。

a = [1, 2, 3]  # a[0] → int(1)
               # a[1] → int(2)
               # a[2] → int(3)

a[0] += 1      # a[0] → int(2)
a[1] += 1      # a[1] → int(3)
a[2] += 1      # a[2] → int(4)

间接更新

那我称之为“间接”更新呢?

for x in a:
  x += 1

展开循环会产生这一系列等效的语句:

x = a[0]
x += 1
x = a[1]
x += 1
x = a[2]
x += 1

每一步发生了什么,为什么数组没有改变?

x = a[0]

这使得x 引用任何对象a[0] 引用。 a[0]x 都引用同一个 int(1) 对象,但 x 不直接连接到 a[0]。它指的是a[0] 所指的任何东西,而不是a[0] 本身。

x += 1

这会改变x 所指的内容。对a[0]没有影响。

第二个和第三个作业也会发生同样的事情。结果是x 不断更改,而a 的元素仅被读取但从未修改。所以当循环终止时a 是不变的。

a = [1, 2, 3]  # a[0] → int(1)
               # a[1] → int(2)
               # a[2] → int(3)

x = a[0]       # x → int(1)
x += 1         # x → int(2)
x = a[1]       # x → int(2)
x += 1         # x → int(3)
x = a[2]       # x → int(3)
x += 1         # x → int(4)

【讨论】:

    【解决方案4】:

    把它想象成你的第一个循环,x 只是 a 中每个元素的替代品。这不是a 中的实际元素(尽管id() 是因为它们都引用了同一个对象)。当您执行x += 1 时,您只是更改了x 的值,而不是列表中的值。

    在您的第二个 for 循环中,您实际上是通过执行 a[i] += 1 来修改列表。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-01
      • 1970-01-01
      • 2018-02-06
      • 2017-03-31
      • 2016-01-11
      • 2012-03-23
      • 1970-01-01
      • 2014-06-30
      相关资源
      最近更新 更多