SunnyIsaLearner
SunnyIsaLearner

Reputation: 780

Python confusion -- convention, name and value

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

Answers (4)

Mad Physicist
Mad Physicist

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

willeM_ Van Onsem
willeM_ Van Onsem

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

MSeifert
MSeifert

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

Nullman
Nullman

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

Related Questions