Recall last time's discussion with respect to lists. Be sure to do the reading in the text book on this. The absolute key to understanding this is to observe that variables are really just names that are associated with objects or more formally variables are just object references. There is a difference between a thing and a name referring to the thing. One object may have many names (aliases) but a name can only refer to one thing at a time. In Python we call the list of associations of names to things the namespace. You can think of it like a map from names to things. Let's look at some examples of aliasing, copying, and parameter passing to try and understand how all of these things work in Python.
l1=[1,2,3]
l2=l1 # l2 is an alias l1 and l2 both point to the same thing
l1.append(4)
l2
In the example above, l2 is just another object reference for the same object that l1 refers to. This is called an alias. Whatever we do using the object reference l1 will affect l2 and vice-versa.
l1=[1,2,3]
l2=l1[:] # this time l2 is a copy of l1.
l1.append(4)
l2
This l2 is a copy of l1 so in fact there are two different list objects each with their own separate names. Therefore when we do something using the object reference l1 it doesn't necessarily affect l2 and vice-versa.
l1=[1,2,['a','b']] # the list l1 has another list as its third element
l2=l1[:]
l1.append(4)
l2
Like before, since l2 is a copy of l1, appending a value to l1 has no effect on on l2.
l1=[1,2,['a','b']] # the list l1 has another list as its third element
l2=l1[:]
l1[2].append('c')
l2
So what happened here? As before, l2 is a copy of l1, that is to say there are two different objects, l1 and l2, yet this time changing the object referred to by l1 did have an effect on the object referred to by l2. There is nothing inconsistent here. We just have to remember what a list object is. That is, it's an ordered collection of object references (not actual objects, but object references). So although l2 is a separate object with its own sequence of object references that are different from l1's object references, they refer to the same objects. That is, when we copied l1 we only copied the object references and not the actual objects they refer to. So when I follow the third object reference in l1 to the object it's referring to, it's the same object that the third object reference in l2 is referring to. Another way of saying that is that l2[2] and l1[2] are aliases. So when I mutate (change) one, I change the other.
Now check this out:
l1=[1,2,['a','b']]
l2=l1[:]
l1[2]=['a','b','c']
l2
So if l1[2] and l2[2] are aliases, why didn't changing l1[2] change l2[2]? Again, everything here is consistent. We must only observe that we didn't actually change l1[2]. What we did was reassign the object reference l1[2] to a completely different object. By using the assignment operator (equals sign) we created a brand new list object ['a', 'b', 'c'] . It's no different than doing this:
x=2
y=x
x=3
y
In the above, x and y were aliases but then we reassigned x to something else.
Summary: All you have to remember to keep this straight is the following
Parameter Passing: Once we are comfortable with how object references work, parameter passing (The act of copying argument values into parameters when a function or method is called.) is not hard to understand. The key here is to realize that the argument values are just object references. That's all. So the parameters will now be aliases for the same objects giving the function access to those objects. If we mutate the objects they refer to within the function body (this is called a side effect, something we do not want to do) then the change will be apparent outside the function. If we reassign the parameters altogheter in the function body then it will not. Here are some examples:
def f(a):
a.append(10)
x=[0,1,2]
f(x)
x
The value of x, namely an object reference referring to a specific list is copied into a. Therefore a refers to the same object that x does. We mutate that object and therefore it affects x. We want to avoid this.
def f(a):
a=[0,1,2,10]
x=[0,1,2]
f(x)
x
So this time we reassigned the parameter a to a new list. This had no effect on x.
In the end, don't worry about what's mutable and what's not. Just understand that reassigning is different than mutating.
One last thing: I said up there that side effects were bad. so what if I really do want to change x using a function? Well tha's what return statements are for:
def f(a):
return [0,1,2,10]
x=[0,1,2]
x=f(x)
x
Next time you have your midterm. There are 20 multiple choice questions and 4 short functions you must create. I have loaded some sample questions on courseworks. We can look at them now.
Good luck!