我是初学者,在学习python时会感到困惑。如果我有以下python代码:
import numpy as np
X = np.array([1,0,0])
Y = X
X[0] = 2
print Y
Y将显示为array([2, 0, 0])
但是,如果我执行以下操作:
import numpy as np
X = np.array([1,0,0])
Y = X
X = 2*X
print Y
Y
仍为array([1,0,0])
发生了什么事?
答案 0 :(得分:8)
这样想: python中的等号分配引用。
Y = X
使Y指向X指向
X[0] = 2
使x [0]指向2
X = 2*X
使X指向一个新东西,但Y仍指向原始X的地址,因此Y未更改
这不完全正确,但它足够接近理解原则
答案 1 :(得分:5)
因为X
和Y
引用指向同一个对象np.array([1,0,0])
,这意味着无论是否完成了通话通过 X
或Y
,结果将是相同的,但更改一个的引用,无效。
如果你写:
X = np.array([1,0,0])
Y = X
基本上发生的事情是,有两个局部变量 X
和Y
引用到同一个对象。所以内存看起来像:
+--------+
Y -> |np.array| <- X
+--------+
|[1,0,0] |
+--------+
现在,如果你做的X[0] = 2
基本上是短的:
X.__setitem__(0,2)
所以你在对象上调用一个方法。所以现在内存看起来像:
+--------+
Y -> |np.array| <- X
+--------+
|[2,0,0] |
+--------+
如果你写了:
X = 2*X
第一个2*X
已评估。现在2*X
是简称:
X.__rmul__(2)
(Python首先查看2
是否支持__mul__
的{{1}},但由于X
将引发2
),Python将回退到{{1} }})。现在NotImplementedException
不会更改X.__rmul__
:它会保留X.__rmul__
完整,但会构建一个新数组并返回该数组。 X
捕获现在引用该数组的新数组。
创建一个新的X
对象:X
,然后array
引用该新对象。所以现在内存看起来像:
array([4, 0, 0])
但正如您所见,X
仍然引用旧对象。
答案 2 :(得分:3)
这更多是关于约定和名称而不是引用和值。
分配时:
Y = X
然后名称Y
引用名称X
指向的对象。在某种程度上,指针X
和Y
指向同一个对象:
X is Y # True
is
检查名称是否指向同一个对象!
然后它变得棘手:你在阵列上做了一些操作。
X[0] = 2
这称为“项目分配”并调用
X.__setitem__(0, 2)
__setitem__
应该做什么(约定)是更新容器X
中的某个值。因此,X
之后仍应指向同一个对象。
然而X * 2
是“乘法”,并且约定声明这应该创建一个新对象(再次约定,您可以通过覆盖X.__mul__
来改变该行为)。所以当你这样做时
X = X * 2
名称X
现在引用X * 2
创建的新对象:
X is Y # False
通常常见的库遵循这些约定,但重要的是要强调你可以完全改变它!
答案 3 :(得分:2)
当您说 +--------+ +--------+
Y -> |np.array| X ->|np.array|
+--------+ +--------+
|[2,0,0] | |[4,0,0] |
+--------+ +--------+
时,您创建的对象包含一些方法和一些内部缓冲区,其中包含实际数据和其他信息。
执行Y
设置X = np.array([1, 0, 0])
以引用相同的实际对象。这称为绑定到Python中的名称。您已将绑定到Y = X
的同一对象绑定到名称Y
。
执行X
调用对象的Y
方法,该方法对底层缓冲区执行一些操作。如果修改了对象。现在,当您打印X[0] = 2
或__setitem__
的值时,来自该对象缓冲区的数字为X
。
执行Y
会转换为2, 0, 0
。此方法不会修改X = 2 * X
。它创建并返回一个新的数组对象,每个数组对象的元素是X.__rmul__(2)
的相应元素的两倍。然后将新对象绑定到名称X
。但是,名称X
仍然绑定到原始数组,因为您没有做任何更改。另外,使用X
是因为Y
不起作用。 Numpy数组自然地将乘法定义为可交换,因此X.__rmul__
和2.__mul__(X)
应该是相同的。
值得注意的是,您还可以执行X.__mul__
,这会将更改传播到X.__rmul__
。这是因为X *= 2
运算符转换为Y
方法, 修改输入。