14. Aliases & List Trivia
The following code should be simple to understand at this stage
1a = 5
2b = a
3print(a, b) # Results in 5 5
4
5b = 7
6print(a, b) # Results in 5 7 --- a is left unchanged
And similarly, the below example should not surprise you
1a = [0, 1, 2]
2b = a # b is an "alias" for a
3print(a, b) # Results in [0, 1, 2] [0, 1, 2]
4
5b = [5, 6, 7] # Change what b references
6print(a, b) # Results in [0, 1, 2] [5, 6, 7] --- a is left unchanged
However, the following may throw you off
1a = [0, 1, 2]
2b = a # b is an "alias" for a
3print(a, b) # Results in [0, 1, 2] [0, 1, 2]
4
5b[1] = 99 # Change index 1 of the list b references
6print(a, b) # Results in [0, 99, 2] [0, 99, 2]
Remember,
a
andb
are both referencing the same listThey are aliases
There is only one list, but we have two references to that one list
Regardless of the variable used to modify the single list, it’s the list that is being altered
If you expected the line
b = a
to make a full copy of the list referenced bya
, then this will seem strangeBut the line
b = a
does not make a copy of the list — it makes a copy of the reference to the list
Warning
This idea of references and aliases goes well beyond lists and will come up more as we progress through the course. It is something you will get used to with practice, but be aware that mixing up references is a very common error even experienced programmers run into.
If you do actually want to make a copy of a list, there are a few ways to do it
Lists have a copy method that returns a copy
new_list = some_list.copy()
It is also possible to slice the list to produce a copy
new_list = some_list[:]
Activity
Create a list
l
with arbitrary contents.Create an alias of
l
calledl_alias
.Create a copy of
l
calledl_copy
.
Convince yourself that you did in fact make an alias with l_alias
and a copy with l_copy
,
14.1. Functions and Aliasing
When a list is given to a function, the parameter will get a reference to the list and not a copy of the list
The parameter within the function will be an alias
1def add_to_list(some_list, value):
2 some_list.append(value)
3
4a_list = ['a', 'b', 'c']
5add_to_list(a_list, 99)
6print(a_list) # Results in ['a', 'b', 'c', 99]
In the above example, although never access through
a_list
, the lista_list
references is altered through the aliassome_list
within the functionadd_to_list
14.1.1. Side Effects & Pure Functions
add_to_list
is an example of a function that has a side effectThe function modified the list that was passed by reference
The term side effect comes from our mathematical expectation of a function
A function maps some parameters on to a value
If I give you the function \(f(x, y, z)= x + y - z\) and ask you to evaluate \(f(1, 2, 3)\), you don’t expect the values of \(x\), \(y\), and \(z\) to change
We can write a different version of the function that has no side effect
Functions without side effects are called pure functions
1def add_to_list_pure(some_list, value):
2 new_list = some_list.copy()
3 new_list.append(value)
4 return new_list
5
6a_list = ['a', 'b', 'c']
7other_list = add_to_list_pure(a_list, 99)
8print(a_list) # Results in ['a', 'b', 'c']
9print(other_list) # Results in ['a', 'b', 'c', 99]
In the new function
add_to_list_pure
, the function makes a copy of the list passed by reference and made changes to the copyThe new list was returned
In the end, the original list’s data was left alone
There are nice theoretical and practical benefits to keeping functions pure
But that does not mean that non-pure functions are intrinsically bad
Sometimes it’s just a lot easier to achieve something with side effects
14.2. List Trivia
We can find the length of a list
1some_list = [10, 11, 12]
2print(len(some_list)) # Results in 3
We can have empty lists
1empty_list = []
2print(empty_list) # Results in []
3print(type(empty_list)) # Results in <class 'list'>
4print(len(empty_list)) # Results in 0
We can have lists of lists
1some_nested_lists = [[0, 1, 2], ['a', 'b', 'c']]
2print(some_nested_lists[1]) # Results in ['a', 'b', 'c']
3print(some_nested_lists[1][0]) # Results in 'a'
We can append to a list
1some_list = [10, 11, 12]
2some_list.append(99)
3print(some_list) # Results in [10, 11, 12, 99]
We can concatenate lists with
+
to create a new listThe original lists are left unchanged
1list_a = [0, 1, 2]
2list_b = ['a', 'b', 'c']
3list_c = list_a + list_b
4print(list_a) # Results in [0, 1, 2]
5print(list_b) # Results in ['a', 'b', 'c']
6print(list_c) # Results in [0, 1, 2, 'a', 'b', 'c']
We can repeat a list with
*
1some_list = [10, 11, 12]
2print(some_list * 3) # Results in [10, 11, 12, 10, 11, 12, 10, 11, 12]
Activity
Python has some built in functions that we can use on lists:
min
max
sum
However, just because Python provides these functions, someone still had to write these functions.
Without using the built in
sum
, write your own functionmy_sum
to add up the contents of a list.How different do you think your algorithm is compared to the one Python gave you?
If you had a list of length \(10\), how many things does your function need to add together?
What if your list was length \(10,000\)?