python surprises
Here are 4 attempts to give a function some numbers so that the function can print double the numbers. Some of the versions use numbers*=2
instead of numbers= numbers*2
, some use a NumPy array
instead of a list
. The output the programs produce is shown too. You might expect all the versions to give the same answer. Actually they all produce different ones -
# 1. Using Numpy and "numbers=numbers*2" import numpy as np def doubling(numbers): numbers=numbers*2 print('At the end of doubling, numbers=',numbers) scores = np.array([1,2,3]) print ('Before calling doubling, scores=',scores); doubling(scores) print ('After calling doubling, scores=',scores); Output Before calling doubling, scores= [1 2 3] At the end of doubling, numbers= [2 4 6] After calling doubling, scores= [1 2 3] |
# 2. Using Numpy and "numbers*=2" import numpy as np def doubling(numbers): numbers *=2 print('At the end of doubling, numbers=',numbers) scores = np.array([1,2,3]) print ('Before calling doubling, scores=',scores); doubling(scores) print ('After calling doubling, scores=',scores); Output Before calling doubling, scores= [1 2 3] At the end of doubling, numbers= [2 4 6] After calling doubling, scores= [2 4 6] |
# 3. Using list and "numbers=numbers*2" def doubling(numbers): numbers =numbers*2 print('At the end of doubling, numbers=',numbers) scores = [1,2,3] print ('Before calling doubling, scores=',scores); doubling(scores) print ('After calling doubling, scores=',scores); Output Before calling doubling, scores= [1, 2, 3] At the end of doubling, numbers= [1, 2, 3, 1, 2, 3] After calling doubling, scores= [1, 2, 3] |
# 4. Using list and "numbers*=2" def doubling(numbers): numbers *=2 print('At the end of doubling, numbers=',numbers) scores = [1,2,3] print ('Before calling doubling, scores=',scores); doubling(scores) print ('After calling doubling, scores=',scores); Output Before calling doubling, scores= [1, 2, 3] At the end of doubling, numbers= [1, 2, 3, 1, 2, 3] After calling doubling, scores= [1, 2, 3, 1, 2, 3] |
Variables and objects
To explain the output, first we need to look at variables in more detail. When the following is run
a="foo"
a string object is created with the value foo
and the variable a
refers to that object. The string object can't subsequently be changed, because in Python strings are immutable
but the variable a
might later refer to a different object.
Assigning a value to a variable sometimes creates a new object, and sometimes creates an alias. Running
a="foo" b=a print(a,b)
gives (unsurprisingly)
foo foo
Diagrammatically the situation is as in the figure on the right. If you then do
print (id(a), id(b))
you'll find that a
and b
refer to the same underlying object. However if you then do
a="bar" print (a,b) print (id(a), id(b))
you'll find that a
and b
now refer to different objects that have different values - a
was originally referring to a string which can't be changed, so a="bar"
forces the creation of a new object which a
then refers to. Contrast that with the following situation
x=[ 1, 2, 3] y=x print (x,y) // output is [1, 2, 3] [1, 2, 3] x[1]="middle" print (x,y) // output is [1, 'middle', 3] [1, 'middle', 3]
Here x
refers to a list object. It's mutable, so can be changed. y
refers to the same underlying object, as print (id(x), id(y))
would show.
a == b
will tell you whether the 2 variables have the same value (i.e. whether the objects they refer to have the same value) . a is b
will tell you whether the variables refer to the same object.
An explanation of the difference between programs 1 and 2
Programs 1 and 2 (also 3 and 4) differ in the way they double the numbers. Note that numbers*=2
and numbers= numbers*2
don't do the same thing. The difference becomes clearer if you run this code
def doubling(numbers): print('id of numbers before doubling=',id(numbers)) numbers *=2 print('At the end of doubling, numbers=',numbers) print('id of numbers after doubling=',id(numbers)) scores = [1,2,3] print ('Before calling doubling, scores=',scores); doubling(scores) print ('After calling doubling, scores=',scores);
The id of numbers
is unchanged. But if you change numbers*=2
to numbers= numbers*2
you'll see that the id does change. Essentially, numbers*=2
changes the values in the object it's given, whereas numbers= numbers*2
creates a new object that numbers
subsequently refers to, leaving the original object unchanged. So in programs 2 and 4 (which use numbers= numbers*2
) the original scores
is untouched.
An explanation of the difference between programs 1 and 3
Programs 1 and 3 (also 2 and 4) differ in the way scores
is created. In program 3 and 4 scores
is a list, and multiplying a list by 2 duplicates the list. In programs 1 and 2, scores
is a NumPy array, and multiplying has a more conventional effect.