在'for'循环中,i = i + 1和i + = 1有什么区别?

我今天发现了一个奇怪的东西,想知道是否有人能够弄清楚这里有什么不同?

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

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


只是为了完整:

x += y 并不总是在原地进行,至less有三个例外:

  • 如果x 没有实现 __iadd__方法,那么x += y语句只是x = x + y的简写。 这是如果x是像int一样的情况。

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

  • __iadd__方法在理论上可以实现不适用。 不过,这样做真的很奇怪。

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

您可以在“仿真数字types”的Python文档中阅读更多信息。

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

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

请参阅关于7.2.1的部分。 增强的赋值语句 :

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

+=运算符__iadd__ 。 该函数在原地进行更改,只有在执行后,结果才会被设置回“应用” +=的对象。

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

正如已经指出的那样, 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现在指的是一个新的整数,而不是以任何方式耦合到C 当你遍历整形后的数组时,整行( np.ndarray )一次被分配给b (和a ),这是可变对象,这意味着你可以np.ndarray使用新的整数,当你做a += 1

应该指出的是,虽然++=被认为是相关的(通常是很常见的),但任何types都可以通过定义__add____iadd__方法来实现它们。

短格式( a += 1 )可以select修改a就地,而不是创build一个新的对象表示总和并重新命名为相同的名称( a = a + 1 )。所以,短格式a += 1 )非常有效,因为它不一定需要复制a不同的a = a + 1

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

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

在第一个循环中, a = a + 1的计算方法如下: __add__(self, other)函数。 这创build了一个新的对象,因此,A不被修改。 之后,variablesa被设置为引用结果。

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

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