The fact that X and Y are references to the same np.array([1,0,0]) object np.array([1,0,0]) , this means that regardless of whether the call is made via X or Y , the result will be the same, but the change Linking one has no effect.
If you write:
X = np.array([1,0,0]) Y = X
basically what happens is that there are two local variables X and Y that refer to the same object. Thus, the memory looks like this:
+--------+ Y -> |np.array| <- X +--------+ |[1,0,0] | +--------+
Now, if you do X[0] = 2 , which basically is not suitable for:
X.__setitem__(0,2)
therefore you are calling a method on an object . So now the memory looks like this:
+--------+ Y -> |np.array| <- X +--------+ |[2,0,0] | +--------+
If you, however, write:
X = 2*X
evaluated first 2*X Now 2*X not suitable for:
X.__rmul__(2)
(Python first looks to see if 2 __mul__ for X , but since 2 will raise a NotImplementedException ), Python will revert to X.__rmul__ ). Now X.__rmul__ does not change X : it leaves X intact , but creates a new array and returns this. X catches this new array, which now refers to this array).
which creates a new array object: array([4, 0, 0]) , and then X links to this new object . So now the memory looks like this:
+--------+ +--------+ Y -> |np.array| X ->|np.array| +--------+ +--------+ |[2,0,0] | |[4,0,0] | +--------+ +--------+
But, as you can see, Y still links to the old object .