【问题标题】:What is the difference between i = i + 1 and i += 1 in a 'for' loop? [duplicate]'for' 循环中的 i = i + 1 和 i += 1 有什么区别? [复制]
【发布时间】:2017-05-17 17:47:36
【问题描述】:

我今天发现了一件奇怪的事情,想知道是否有人可以阐明这里有什么不同?

import numpy as np

A = np.arange(12).reshape(4,3)
for a in A:
    a = a + 1

B = np.arange(12).reshape(4,3)
for b in B:
    b += 1

在运行每个for 循环后,A 没有更改,但B 已向每个元素添加了一个。我实际上使用B 版本在for 循环中写入初始化的NumPy 数组。

【问题讨论】:

  • i = i + 1 重新分配 ii += 1i 递增 1
  • 您是否意识到您正在迭代数组的行,而不是单个元素?
  • 下一行 i += 1 在汇编语言级别阻止一条指令
  • 昨天的问题怎么可能得到 78 票?一天内获得近 80 次投票和 6630 次观看?这是一个错误还是什么?
  • @H.Doe:不,这不是错误。感谢所有点赞的人,感谢那些喜欢回答简单问题而不是寻找合适的重复项的人,以及右侧的HNQ 列表。

标签: python loops numpy operators


【解决方案1】:

不同之处在于,一个修改数据结构本身(就地操作)b += 1,而另一个只是重新分配变量a = a + 1


只是为了完整性:

x += y并不总是进行就地操作,有(至少)三个例外:

  • 如果x 没有实现 __iadd__ 方法,那么x += y 语句只是x = x + y 的简写。如果x 类似于int,就会出现这种情况。

  • 如果__iadd__ 返回NotImplemented,Python 将回退到x = x + y

  • 理论上可以实现__iadd__ 方法以使其无法正常工作。不过,这样做真的很奇怪。

碰巧你的bs 是numpy.ndarrays,它实现了__iadd__ 并返回自己,所以你的第二个循环就地修改了原始数组。

您可以在Python documentation of "Emulating Numeric Types" 中了解更多信息。

调用这些 [__i*__] 方法来实现增强的算术赋值(+=-=*=@=/=//=%=**=<<=>>=&=^=|=)。这些方法应该尝试就地执行操作(修改 self)并返回结果(可能是但不一定是 self)。如果未定义特定方法,则扩充分配回退到正常方法。例如,如果 x 是具有 __iadd__() 方法的类的实例,则 x += y 等效于 x = x.__iadd__(y) 。否则,将考虑x.__add__(y)y.__radd__(x),与评估x + y 一样。在某些情况下,增强赋值可能会导致意外错误(请参阅Why does a_tuple[i] += ["item"] raise an exception when the addition works?),但这种行为实际上是数据模型的一部分。

【讨论】:

  • 附上顶部评论以获得较少技术性的解释:a=a+1 这里在功能上等同于 dummy=a+1,因为等号左侧的 a 被重新分配为不再是A 的视图。由于+= 不会像= 那样重新分配,而是修改“就地”,b 仍然是B 的视图,因此BA 没有的地方递增。
  • 有一个非常重要的考虑因素,尤其是在使用 numpy 时。使用in place add时,保留原变量的数据类型。因此,例如,如果您尝试将复数值添加到实数值 numpy 数组中,则原地添加将保持实数值,而如果您使用重新分配,则会得到正确的复数值。使用 numpy 就地添加时要小心。
  • @nevsan 在这种情况下会引发TypeError,因此虽然它可能无法按预期工作,但它不会静默失败或按照您的评论建议做错误的事情。
  • 谢谢!澄清一下,我确实意识到 i += 1 和 i = i + 1 不是一回事,但是由于我使用的是 Numpy,我认为 i = i + 1 仍然意味着我正在写入 i 的相同内存A.
  • @Dammi Numpy 无法更改名称与对象的链接方式,这完全由 python 本身决定。但是要显式写入相同的内存,您可以使用i[:] = i+1(但通常最好使用+= 或几个ufunc 的out-argument)。
【解决方案2】:

在第一个示例中,您正在重新分配变量 a,而在第二个示例中,您正在使用 += 运算符就地修改数据。

7.2.1. Augmented assignment statements 部分:

x += 1 这样的增强赋值表达式可以重写为x = x + 1 以实现类似但不完全相同的效果。在增强版本中,x 只被评估一次。 此外,如果可能,实际操作会在原地执行,这意味着不是创建新对象并将其分配给目标,而是修改旧对象。

+= 操作员调用__iadd__。此函数在原​​地进行更改,并且仅在其执行后,结果才被设置回您正在“应用”+= 的对象。

另一方面,__add__ 接受参数并返回它们的总和(不修改它们)。

【讨论】:

    【解决方案3】:

    正如已经指出的,b += 1 就地更新b,而a = a + 1 计算a + 1 然后将名称a 分配给结果(现在a 不指代A 不再)。

    要正确理解+= 运算符,我们还需要理解可变不可变 对象的概念。考虑一下当我们省略 .reshape 时会发生什么:

    C = np.arange(12)
    for c in C:
        c += 1
    print(C)  # [ 0  1  2  3  4  5  6  7  8  9 10 11]
    

    我们看到C没有更新,这意味着c += 1c = c + 1 是等价的。这是因为现在C 是一个一维数组(C.ndim == 1),所以当迭代C 时,每个整数元素都会被拉出并分配给c

    现在在 Python 中,整数是不可变的,这意味着不允许就地更新,有效地将 c += 1 转换为 c = c + 1,其中 c 现在指的是 new 整数,而不是以任何方式与C 耦合。当您遍历重构的数组时,整行(np.ndarray's)一次分配给b(和a),它们是可变对象,这意味着你被允许随意插入新的整数,当你这样做时会发生a += 1

    应该提到的是,尽管++= 意味着如上所述(并且通常是)相关联,但任何类型都可以通过定义__add__ 和@987654321 以任何方式实现它们@ 方法。

    【讨论】:

      【解决方案4】:

      短形式(a += 1)可以选择就地修改a,而不是创建一个表示总和的新对象并将其重新绑定回相同的名称(a = a + 1)。所以,短form(a += 1) 非常高效,因为它不一定需要复制a,不像a = a + 1

      即使它们输出相同的结果,请注意它们是不同的,因为它们是独立的运算符:++=

      【讨论】:

        【解决方案5】:

        首先:循环中的变量 a 和 b 引用 numpy.ndarray 对象。

        在第一个循环中,a = a + 1 的评估如下:numpy.ndarray__add__(self, other) 函数被调用。这会创建一个新对象,因此不会修改 A。之后,变量a 设置为引用结果。

        在第二个循环中,没有创建新对象。语句b += 1 调用numpy.ndarray__iadd__(self, other) 函数,该函数修改了b 所指的ndarray 对象。因此,B 被修改。

        【讨论】:

          【解决方案6】:

          这里的一个关键问题是这个循环遍历B 的行(第一维):

          In [258]: B
          Out[258]: 
          array([[ 0,  1,  2],
                 [ 3,  4,  5],
                 [ 6,  7,  8],
                 [ 9, 10, 11]])
          In [259]: for b in B:
               ...:     print(b,'=>',end='')
               ...:     b += 1
               ...:     print(b)
               ...:     
          [0 1 2] =>[1 2 3]
          [3 4 5] =>[4 5 6]
          [6 7 8] =>[7 8 9]
          [ 9 10 11] =>[10 11 12]
          

          因此+= 作用于一个可变对象,一个数组。

          这在其他答案中有所暗示,但如果您关注的是 a = a+1 重新分配,则很容易错过。

          我还可以使用[:] 索引对b 进行就地更改,甚至可以使用更高级的b[1:]=0

          In [260]: for b in B:
               ...:     print(b,'=>',end='')
               ...:     b[:] = b * 2
          
          [1 2 3] =>[2 4 6]
          [4 5 6] =>[ 8 10 12]
          [7 8 9] =>[14 16 18]
          [10 11 12] =>[20 22 24]
          

          当然,对于像B 这样的二维数组,我们通常不需要对行进行迭代。许多适用于单个B 的操作也适用于整个事情。 B += 1B[1:] = 0

          【讨论】:

            猜你喜欢
            • 2011-01-19
            • 2014-01-24
            • 1970-01-01
            • 1970-01-01
            • 2014-08-09
            • 1970-01-01
            • 2017-10-23
            相关资源
            最近更新 更多