Lab 8, Task 1 part 1 ------ After executing the statements in the console, evaluating values displays the following list: [2, 0, 6, 1] Note that the values at positions 1 and 3 have been changed. part 2 ------ After defining the function foo(), the program begins with assignments to the global variables. The first assignment creates a new list in memory and assigns a reference to that list to the variable a: ------------- global: | _ | a |-|--+--------> [4, 5, 6] | ------------- The second assignment creates another new list in memory that is an exact copy of a, and assigns a reference to that list to the variable b. When a list slice is used, a new list will always be created: ------------- global: | _ | a |-|--+--------> [4, 5, 6] _ | b |-|--+--------> [4, 5, 6] | ------------- The third assignment simply assigns the reference held in a to the variable c. Therefore, no new list is created, but a and c share a reference to the same list: ------------- global: | _ | a |-|--+--------> [4, 5, 6] | -------^ _ | | b |-|--+---|----> [4, 5, 6] _ | | c |-|--+---- | ------------- The fourth assignment assigns the integer 5 to the variable x. And because integers are immutable (meaning that you can't change their internals), we can picture the 5 as being inside the variable itself: ------------- global: | _ | a |-|--+--------> [4, 5, 6] | -------^ _ | | b |-|--+---|----> [4, 5, 6] _ | | c |-|--+---- _ | x |5| | | ------------- The next part of the program calls the function foo(), so we get another stack frame for the local variables that belong to foo(). And because the call is made using c and x as inputs, foo()'s parameters get copies of the contents of those variables: ------------- foo: | _ | values |-|--+----------- _ | | x |5| | | | | ------------- | global: | | _ | V a |-|--+--------> [4, 5, 6] | -------^ _ | | b |-|--+---|----> [4, 5, 6] _ | | c |-|--+---- _ | x |5| | | ------------- Note in particular that the parameter values gets a copy of the *reference* that is stored in c. It doesn't get a copy of the list. Thus, values refers to the same list that c refers to -- which is also the list that a refers to! The first statement in foo() performs item assignment: values[1] = 2* values[1] and thus it modifies the internals of the list to which values refers -- changing the value of position 1 from 5 to 10. ------------- foo: | _ | values |-|--+----------- _ | | x |5| | | _ | | i |2| | | | | ------------- | global: | | _ | V a |-|--+--------> [4, 10, 6] | -------^ _ | | b |-|--+---|----> [4, 5, 6] _ | | c |-|--+---- _ | x |5| | | ------------- The function next increments the value in its local variable x: ------------- foo: | _ | values |-|--+----------- _ | | x |6| | | _ | | i |2| | | | | ------------- | global: | | _ | V a |-|--+--------> [4, 10, 6] | -------^ _ | | b |-|--+---|----> [4, 5, 6] _ | | c |-|--+---- _ | x |5| | | ------------- Note that this does *not* affect the global x. After incrementing x, the function has no more code to execute. Therefore, it's ready to return, and because the function doesn't specify a return value, the special value None is returned. If you trace the code in Python Tutor, you will see that it shows None as the return value just before the function is about to return. Returning from the function removes the stack frame for foo() and brings us back to the global scope: ------------- global: | _ | a |-|--+--------> [4, 10, 6] | -------^ _ | | b |-|--+---|----> [4, 5, 6] _ | | c |-|--+---- _ | x |5| | | ------------- Notice that the variables a and c refer to the same list that was changed in foo, so printing either of those variables will show the changed list. The variable b refers to a separate list that was not changed, so we still see the original values. And the global x has not been changed. Thus, the output of the program is: a = [4, 10, 6] b = [4, 5, 6] c = [4, 10, 6] x = 5