Python Arena
Step 3 of 4

Lists as parameters

A function call works like today = todo: the parameter gets a reference to the list you give as the argument, not a copy of it. The function and the code that calls it share one list, so a function can change a list it's given:

def add_bonus(scores: list):
    for i in range(len(scores)):
        scores[i] += 5


points = [10, 20, 30]
add_bonus(points)
print(points)
Output
[15, 25, 35]

add_bonus has no return. It does its job by changing the list, so you call it on a line of its own, for what it does, not for a value.

The other way is to leave the original alone and return a new list:

def with_bonus(scores: list) -> list:
    result = []
    for score in scores:
        result.append(score + 5)
    return result


points = [10, 20, 30]
new_points = with_bonus(points)
print(points)
print(new_points)
Output
[10, 20, 30]
[15, 25, 35]

Both are fine. What matters is that you know, and say, which one a function does: does it change the list it's given, or return a new one?

Assigning to the parameter changes nothing outside

Here is a common mistake. This function should cut the list it's given down to its first count names. It takes them with a slice and assigns the slice to the parameter:

def keep_first(names: list, count: int):
    names = names[:count]


queue = ["Ada", "Ben", "Cleo", "Dev", "Eli"]
keep_first(queue, 3)
print(queue)
Output
['Ada', 'Ben', 'Cleo', 'Dev', 'Eli']

A slice is a new list, and names = names[:count] points the parameter names at it. That's a new value for one variable inside the function, just like today = ["sleep"] in the first step of this module. The list that queue refers to is never touched, and the new list is lost when the function ends.

To change the caller's list, change the list itself: assign to its indexes, as add_bonus does, or replace its items with a slice assignment. A slice on the left side of = replaces those items with the items of the list on the right. There can be fewer or more of them, so the list can get shorter or longer:

In the Python interpreter
>>> letters = ["a", "b", "c", "d", "e"]
>>> letters[1:4] = ["X"]
>>> letters
['a', 'X', 'e']
>>> letters[:] = ["p", "q", "r", "s"]
>>> letters
['p', 'q', 'r', 's']

[:] is the whole list, so letters[:] = ... replaces every item. That's the fix for keep_first: its line becomes names[:] = names[:count]. Python works out the right side first, the slice with the first count names, and then puts its items into the caller's list in place of the old ones.

Your task

Challenge 1 of 3

Write a function cap_scores(scores, limit) that changes the list it's given: every score greater than limit becomes limit. The function returns nothing.

scores = [45, 120, 80, 101]
cap_scores(scores, 100)
print(scores)
[45, 100, 80, 100]

Challenge 2

shout_all(words) should change the list it's given, so that every word is in capital letters with an exclamation mark at the end. It builds the right list, but the caller's list doesn't change. Fix it.

words = ["hey", "stop"]
shout_all(words)
print(words)
['HEY!', 'STOP!']

Challenge 3

Write a function remove_all(items, value) that removes every copy of value from the list it's given. It changes the list and returns nothing. If value isn't in the list, the list stays as it is.

items = [1, 3, 1, 2, 1]
remove_all(items, 1)
print(items)
[3, 2]