Call by object reference

These notes are taken from a mix of GeeksforGeeks, the Hitchiker's Guide to Python, and a few mistakes I made personally!

The tl;dr is: mutable objects act like call-by-reference, while immutable objects act like call-by value.

 Immutable types: call-by-value

So immutable types such as:

will all act like call-by-value.

Mutable types: call-by-reference

Variable Binding

With an immutable type we can collapse the references:

With a mutable type we need to keep separate copies:

Shared References

Remember integers are immutable:

Lists of integers however, are mutable:

:surprisedpikachu:

At first this behaviour can seem surprising, but once you understand that mutable and immutable objects are treated differently it will start to seem natural.

Example: Shared References

In Python we can multiply lists by scalars to repeat them:

So what does a[0] = 5 do?

How about we want to make a 2D array/matrix?

Why does this happen?

When we do [0]*5, we get a list of integers. Recall that int is an immutable type: that means the address of each item in the list will be different, and when we change one item the others keep their value.

When we take a list [0,0,0,0,0] and we multiply it by 5, we get a list of lists. Recall that list is a mutable type. So the addresses of every list are the same. When we change one item in the list we end up changing the values in all the lists.

How about if we use a list comprehension?

a = [[0]*N for i in range(N)]

[0]*N is still of type list, but note that in a list comprehension the generator ([0]*N) is called N times. That means each list is assigned a new address in memory and is therefore not overwritten by updates to other lists.

Example: In-place Operations

There are two ways to append an item to a list:

  1. a.append(b)
  2. concatenate a + [b]

Method 1:

Method 2:

Notice that method 2 returns something, and method 1 doesn't. That's because .append modifies the array in-place. So if we look at a after a.append(b) we'll get a list [1,2,3,4].

What if we look at a after a+[b]?

It will be the same unless we explicitly modify a:

Notice a has moved location. When we do an in-place operation, does this still happen?

Imagine we have two separate lists, and somewhere in our code the two lists are themselves kept in another list.

We want the value of c to be [[1,2,3],[4,5,6]]. Imagine now we want to make a change to b without changing c. How do we do it?

Will this line change c?

No, because b moved address.

Example: Mutable Default Arguments

One thing I didn't realise at first was that default arguments are evaluated once, even for different instances of a class.

Imagine we have a class whose initialiser has a default value, like an empty list.

We create multiple instances of that class: does each instance have its own list?

So what should we do instead?