Sum of Scores and Computing The Average: Why not to .pop()


#1

A lesson in copying and mutability -- the ability to change an object as it gets passed around, rather than changing a copy of an object you want to pass around.

I'm working the Computing the Average exercise. In the previous Sum of Scores exercise, I figured I could get away with a clever trick with the while loop:

def grades_sum(scores):
    result = 0
    while len(scores)>0:
        result += scores.pop()
    return result

.pop() provides the neat function of taking the last item on a list and returning it to you. It also removes that item from the list itself. In a vacuum, this works great. But if you need to do something else, like compute the average, you've ruined the content of the grades[] object itself because none of the scores are there anymore!

def grades_average(scores):
    denominator = float(len(scores))
    numerator = grades_sum(scores)
    return numerator/denominator

Looks fine, right? The function call even returned and printed the right value, but I get the following validation error:

Oops, try again
grades should contain [100, 100, 90, 40, 80, 100, 85, 70, 90, 65, 90, 85, 50.5]!

Turns out that my grades_sum() function was changing the core content of grades itself! I got around it using copy.deepcopy() (study up to understand why: Python docs 8.17 copy) but it might be worthwhile if the Sum of Scores exercise validated that the content of grades[] didn't change once the sum was computed. The accepted answer is below:

from copy import deepcopy
...
def grades_average(scores):
    destroyer = deepcopy(scores)   
    denominator = float(len(scores))
    numerator = grades_sum(destroyer)
    return numerator/denominator

#2

Which is where the problems begin. scores is a list, and as such is only a reference to an object in global scope. Changes made inside a function are directly reflected in the object. You are effectively wiping out every list that is passed into the function, rendering them unusable to the rest of the program.

We would only use pop() in cases where we do not want to leave the value in the list. An example would be a deck of cards. We start with 52 in the deck, but as we deal we remove them from the deck. So .pop() would be useful in this instance.


#3

Precisely. A bit of trial and error was needed to determine how Python treats reference vs assignment in this particular scope. (Also why I dove into copy.deepcopy.) It wasn't immediately obvious given that the grades_sum() function passed muster.


#4

One way to get around the reference object problem is to create a shallow copy of the list and work on it. The original list will not be affected by any destruction of the shallow copy.

temp = scores[:]

Now temp is a copy and we can do to it anything we wish.


#5

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.