Lab 9, Extra-practice exercise 3 After defining the functions, 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 |-|--+--------> [1, 2, 3] | ------------- The second assignment simply assigns the reference held in a to the variable b. Therefore, no new list is created, and a and b share a reference to the same list: ------------- global: | _ | a |-|--+--------> [1, 2, 3] | ----^ _ | b |-|--+------- | ------------- The third assignment creates another new list in memory that is an exact copy of b, and assigns a reference to that list to the variable c. When a list slice is used, a new list will always be created: ------------- global: | _ | a |-|--+--------> [1, 2, 3] | ----^ _ | | b |-|--+------- _ | c |-|--+--------> [1, 2, 3] | ------------- The next part of the program makes the call foo(a). Therefore, we get another stack frame, and foo()'s parameter gets a copy of the reference stored in a. Therefore, values refers to the same list that a and b refer to: ------------- foo: | _ | values |-|--+------------ | | ------------- | global: | | _ | V a |-|--+--------> [1, 2, 3] | ----^ _ | | b |-|--+------- _ | c |-|--+--------> [1, 2, 3] | ------------- The loop inside of foo() is an index-based loop in which the loop variable, i, takes on values that represent the indices of values. Each element of the list is changed to be twice its original value, so values[0] becomes 2, values[1] becomes 4, and values[2] becomes 6. Therefore, at the end of the loop the picture looks like: ------------- foo: | _ | values |-|--+------------ _ | | i |2| | | | | ------------- | global: | | _ | V a |-|--+--------> [2, 4, 6] | ----^ _ | | b |-|--+------- _ | c |-|--+--------> [1, 2, 3] | ------------- When the for loop ends, the function has no more code to execute. Therefore, it returns back to the global scope (returning the special value None) and the stack frame for foo() is removed. ------------- global: | _ | a |-|--+--------> [2, 4, 6] | ----^ _ | | b |-|--+------- _ | c |-|--+--------> [1, 2, 3] | ------------- Notice that the variables a and b reference the same list that was changed in foo, so those changes are still visible using a and b. The next part of the program makes the call bar(c). Therefore, we get another stack frame, and bar()'s parameter gets a copy of the reference stored in c. Therefore, values refers to the same list that c refers to: ------------- bar: | _ | values |-|--+----------------------- | | ------------- | global: | | _ | | a |-|--+--------> [2, 4, 6] | | ----^ | _ | | | b |-|--+------- | _ | | c |-|--+--------> [1, 2, 3] <-- | ------------- The loop inside of the foo function is an element-based loop. The loop variable, x, therefore takes on values that represent the elements of values. Therefore, each value of the list is loaded into the variable x, and x is then doubled. However, because x is assigned copies of the values from the list, changing x does *not* change the internals of the list. After the loop completes, we have this picture: ------------- bar: | _ | values |-|--+----------------------- _ | | x |6| | | | | ------------- | global: | | _ | | a |-|--+--------> [2, 4, 6] | | ----^ | _ | | | b |-|--+------- | _ | | c |-|--+--------> [1, 2, 3] <-- | ------------- When the for loop ends, the function has no more code to execute. Therefore, it returns back to the global scope (returning the special value None) and the stack frame for bar() is removed. ------------- global: | _ | a |-|--+--------> [2, 4, 6] | ----^ _ | | b |-|--+------- _ | c |-|--+--------> [1, 2, 3] | ------------- Because the internals of the list to which c refers were not changed by bar(), c still has its original value. Finally, the program outputs the following: a = [2, 4, 6] b = [2, 4, 6] c = [1, 2, 3]