# 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.