Mutability, References, and Aliasing

Mutability

An object is mutable if you can modify or change its data.

Mutability - Strings and Lists

Strings vs Lists… Indexing and Assignment

What's the output of the following code? →

a = [1, 2, 3]
b = "123"
a[1] = 1
print(a)
b[1] = "1"
print(b)
[1, 1, 3]
Traceback (most recent call last):
  File "indexing_and_assignment.py", line 5, in <module>
    b[1] = "1"
TypeError: 'str' object does not support item assignment

Lists, Indexing and Assignment

You can change values in a list!

really_famous_cats = ["nermal", "felix", "sylvester"]
really_famous_cats[0] = "garfield"
#  assignment works just fine!

List Methods vs String Methods

The behavior of the methods in lists and strings are consistent with the mutability of each type:

List Methods vs String Methods Example

What does this code output? →

a = "one two three"
b = [2, 1, 3]

result_upper = a.upper()
print(a)
print(result_upper)

result_sort = b.sort()
print(b)
print(result_sort)
one two three
ONE TWO THREE
[1, 2, 3]
None

Variables

What's a variable? →

Variables as References

Imagine variables as names that point to objects:

a = [1, 2, 3]
a ------> [1, 2, 3]

Variables as References Continued

Assignment is just pointing a reference. When a new value is assigned to a name, it's the reference that's being changed.

In the following reassignment example, notice that there are no more names that reference the initial list, [1, 2, 3].

a = [1, 2, 3]
a = [4, 5, 6]
          [1, 2, 3]

a ------> [4, 5, 6]

Aliasing

When one variable is assigned to another variable, both variables end up referring to the same object:

a = [1, 2, 3]
b = a
a ---+ 
     |--> [1, 2, 3]
b ---+ 

Aliasing Continued

The actual list object, [1, 2, 3], now has two names that refer to it. Referencing the same object with more than one name is called aliasing.

In the code below, a and b refer to the same list. What will the values of a and b be if we append 4 to b? →

a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)
[1, 2, 3, 4]
[1, 2, 3, 4]

See in python tutor

Aliasing Continued Some More!

Aliasing causes side effects in mutable objects! However, if an object is immutable, like a string, these side effects don't occur (since the object can't be changed anyway!). What gets printed out here? →

a = "hello" 
b = a
b.upper()
print(a)
print(b)
hello
hello
#  b never changed, so neither did a

And if Aliasing Was Not the Intention

If you'd like to make a new list rather than refer to the same list (that is have each variable point to a different object - though two equal objects)…

a ------> [1, 2, 3]

b ------> [1, 2, 3]

…you can use list slicing, which always gives back a new list. Creating a new list that is equivalent to, but a different object from the original, is called cloning.

Cloning

You can slice out the entire list to clone a list from the start index (0) to end index (len(list_of_elements) - 1):

a = [1, 2, 3]

#  cloned!
b = a[0:3]

Alternatively, there's shortcut to slicing out the whole string (without having to deal with precise start and end indexes)…

Just leave out the start and end (m, n) index from the slice.

b = a[:]

And What About Functions?

When parameters are passed to functions the value is a reference! What will this code print out? →

a = [1, 2, 3]

def add_to_list(stuff):
	stuff.append(1)

add_to_list(a)
print(a)
[1, 2, 3, 1]

See in Python tutor

Kind of Important

Changes made to a mutable object that's an argument to a function can be seen both within and outside of the function (all refer to the same object!).

The following function finds the largest integer in a list of integers, but it inadvertently sorts the original.

#  hm... maybe not the best way to find the greatest, but ...
def find_greatest(numbers):
    numbers.sort()
    return numbers[-1]

my_numbers = [5, 6, 1, 4]
greatest = find_greatest(my_numbers)

# hey wait a second, this isn't the original list!
print('original list:', my_numbers)
print(greatest)