在'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__
方法在理论上可以实现不适用。 不过,这样做真的很奇怪。
碰巧你的b
是numpy.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 += 1
和c = 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 += 1
, B[1:] = 0
等