Reputation: 780
I am a beginner and have a confusion when I am learning python. If I have the following python code:
import numpy as np
X = np.array([1,0,0])
Y = X
X[0] = 2
print Y
Y will be shown to be array([2, 0, 0])
However, if I do the following:
import numpy as np
X = np.array([1,0,0])
Y = X
X = 2*X
print Y
Y
is still array([1,0,0])
What is going on?
Upvotes: 5
Views: 176
Reputation: 114330
When you say X = np.array([1, 0, 0])
, you create an object that has some methods and some internal buffers that contain the actual data and other information in it.
Doing Y = X
sets Y
to refer to the same actual object. This is called binding to a name in Python. You have bound the same object that was bound to X
to the name Y
as well.
Doing X[0] = 2
calls the object's __setitem__
method, which does some stuff to the underlying buffers. If modifies the object in place. Now when you print the values of either X
or Y
, the numbers that come out of that object's buffers are 2, 0, 0
.
Doing X = 2 * X
translates to X.__rmul__(2)
. This method does not modify X
in place. It creates and returns a new array object, each of whose elements is twice the corresponding element of X
. Then you bind the new object to the name X
. However, the name Y
is still bound to the original array because you have not done anything to change that. As an aside, X.__rmul__
is used because 2.__mul__(X)
does not work. Numpy arrays naturally define multiplication to be commutative, so X.__mul__
and X.__rmul__
should to the same thing.
It is interesting to note that you can also do X *= 2
, which will propagate the changes to Y
. This is because the *=
operator translates to the __imul__
method, which does modify the input in place.
Upvotes: 2
Reputation: 476740
That's because X
and Y
are references to the same object np.array([1,0,0])
this means that regardless whether a call is done through X
or Y
, the result will be the same, but changing the reference of 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. So the memory looks like:
+--------+
Y -> |np.array| <- X
+--------+
|[1,0,0] |
+--------+
Now if you do X[0] = 2
that is basically short for:
X.__setitem__(0,2)
so you call a method on the object. So now the memory looks like:
+--------+
Y -> |np.array| <- X
+--------+
|[2,0,0] |
+--------+
If you however write:
X = 2*X
first 2*X
is evaluated. Now 2*X
is short for:
X.__rmul__(2)
(Python first looks if 2
supports __mul__
for X
, but since 2
will raise a NotImplementedException
), Python will fallback to X.__rmul__
). Now X.__rmul__
does not change X
: it leaves X
intact, but constructs a new array and returns that. X
catches by that new array that now references to that array).
which creates an new array
object: array([4, 0, 0])
and then X
references to that new object. So now the memory looks like:
+--------+ +--------+
Y -> |np.array| X ->|np.array|
+--------+ +--------+
|[2,0,0] | |[4,0,0] |
+--------+ +--------+
But as you can see, Y
still references to the old object.
Upvotes: 5
Reputation: 152677
This is more about convention and names than reference and value.
When you assign:
Y = X
Then the name Y
refers to the object that the name X
points to. In some way the pointer X
and Y
point to the same object:
X is Y # True
The is
checks if the names point to the same object!
Then it get's tricky: You do some operations on the arrays.
X[0] = 2
That's called "item assignment" and calls
X.__setitem__(0, 2)
What __setitem__
should do (convention) is to update some value in the container X
. So X
should still point to the same object afterwards.
However X * 2
is "multiplication" and the convention states that this should create a new object (again convention, you can change that behaviour by overwriting X.__mul__
). So when you do
X = X * 2
The name X
now refers to the new object that X * 2
created:
X is Y # False
Normally common libraries follow these conventions but it's important to highlight that you can completly change this!
Upvotes: 3
Reputation: 4279
think of it this way: the equals sign in python assigns references.
Y = X
makes Y point to the same address X points to
X[0] = 2
makes x[0] point to 2
X = 2*X
makes X point to a new thing, but Y is still pointing to the address of the original X, so Y is unchanged
this isn't exactly true, but its close enough to understand the principle
Upvotes: 8