Department of Engineering

IT Services

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.