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

• © Cambridge University, Engineering Department, Trumpington Street, Cambridge CB2 1PZ, UK (map)
Tel: +44 1223 332600, Fax: +44 1223 332662