&= 39;对于' i = i + 1和i + = 1之间有什么区别?环?

时间:2017-01-03 15:27:01

标签: python loops numpy operators

我今天发现了一件奇怪的事情,并想知道是否有人可以了解这里的差异是什么?

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数组。

6 个答案:

答案 0 :(得分:109)

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

为了完整性:

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

  • 如果x 未实施 __iadd__方法,则x += y语句只是x = x + y的简写。如果x类似于int

  • ,就会出现这种情况
  • 如果__iadd__返回NotImplemented,则Python会回归x = x + y

  • 理论上可以实现__iadd__方法不起作用。但是,做到这一点真的很奇怪。

碰巧你的bnumpy.ndarray s,它实现__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?),但此行为实际上是数据模型的一部分。

答案 1 :(得分:28)

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

请参阅有关7.2.1. Augmented assignment statements 的部分:

  

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

+=运营商呼叫__iadd__。此函数进行就地更改,并且只有在执行后,结果才会返回到您正在“应用”+=的对象。

另一方面,

__add__获取参数并返回它们的总和(不修改它们)。

答案 2 :(得分:13)

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

要正确理解+=运算符,我们还需要了解 mutable immutable 对象的概念。考虑一下我们遗漏.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 < / em>整数,不以任何方式耦合到C。循环重新整形的数组时,整行(np.ndarray)一次分配给b(和a),这些 mutable 对象,这意味着你可以随意坚持新的整数,当你a += 1时就会发生这种情况。

应该提到的是,尽管++=意味着如上所述(通常也是如此),但任何类型都可以通过定义{{{}来实现它们。分别为1}}和__iadd__方法。

答案 3 :(得分:4)

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

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

答案 4 :(得分:3)

首先关闭:循环中的变量a和b引用a = a + 1个对象。

在第一个循环中,__add__(self, other)的计算方法如下:调用numpy.ndarray的{​​{1}}函数。这会创建一个新对象,因此不会修改A.然后,变量a被设置为引用结果。

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

答案 5 :(得分:2)

这里的一个关键问题是这个循环遍历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