BYU logo Computer Science

To start this guide, download this zip file.

Functions that mutate

A function takes one or more parameters. Some functions will mutate or change their parameters. For example, a function may take a list as a parameter, and then append or remove things from the list, or change items in the list.

In general, when you write a function, it is a good idea to avoid mutating the parameters you are given. Let’s walk through a few examples

Replacing 2 with 5

Here is a function that mutates a list — it replaces every 2 with a 5. You can find this code in replace_mutate.py:

def replace_two_with_five(items: list[int]):
    """
    Replace every 2 with a 5. Modify the list in place.
    """
    for i in range(len(items)):
        if items[i] == 2:
            items[i] = 5


if __name__ == '__main__':
    numbers = [1, 2, 3, 2]
    print("original list:")
    print(numbers)
    print()
    replace_two_with_five(numbers)
    print("original list:")
    print(numbers)

This will print:

original list:
[1, 2, 3, 2]

original list:
[1, 5, 3, 5]

It is generally preferred to write functions that do NOT mutate their inputs. Whenever possible, try to write functions that don’t mutate.

A version that does not mutate the list

For example, this code will also replace every 2 with a 5, but it returns a new list instead of modifying the original list. You can find this code in replace_without_mutate.py:

def replace_two_with_five(items: list[int]) -> list[int]:
    """
    Replace every 2 with a 5. Modify the list in place.
    """
    new_items = []
    for item in items:
        if item == 2:
            item = 5
        new_items.append(item)
    return new_items


if __name__ == '__main__':
    numbers = [1, 2, 3, 2]
    print("original list:")
    print(numbers)
    print()
    new_numbers = replace_two_with_five(numbers)
    print("original list:")
    print(numbers)
    print()
    print("new list:")
    print(new_numbers)

This will print:

original list:
[1, 2, 3, 2]

original list:
[1, 2, 3, 2]

new list:
[1, 5, 3, 5]

Notice how the original list is unchanged.

Be sure to document whether your function mutates its inputs or not. Write non-mutating functions whenever possible.

Editing words in a phrase

To show you another example of list indexing, here is a small program that replaces words in a phrase. The program works like this:

  • a person enters a phrase, containing multiple words (“hello I am pleased to meet you”)
  • the program loops for as long as you want
    • enter a word (“pleased”)
    • enter a replacement word (“thrilled”)
    • replace the word with its replacement in the phrase (replace “pleased” with “thrilled” in the phrase)

Here is code to do this, which is in edits.py:

def replace_word(phrase: list[str], word: str, other: str):
    """
    <phrase> should be a list that contains multiple words
    <word> and <other> are words

    Replace <word> with <other> in <phrase>.
    This modifies the list in <phrase>.
    """
    for i in range(len(phrase)):
        if phrase[i] == word:
            phrase[i] = other


def main():
    # ask for a phrase (multiple words)
    # spilt it into a list of words
    phrase = input('Phrase: ').split()
    print(phrase)

    # ask for a series of <word>s and <replacement>s
    # replace each instance of <word> with its <replacement> in the
    # phrase
    while True:
        word = input('Word: ')
        if not word:
            break
        other = input('Replacement: ')
        replace_word(phrase, word, other)
        print(phrase)

    # turn the list of words back into a string
    print(' '.join(phrase))


if __name__ == '__main__':
    main()

Some things to note:

  • The replace_word() function modifies — or mutates — the list it is given. It does not return a new list.

  • One of the most common ways you will use range() is to loop through all of the items in a list. You can do this by giving range() the length of the list:

for i in range(len(phrase)):

If you run this code, you can do something like this:

Phrase: You are doing great with this stuff
['You', 'are', 'doing', 'great', 'with', 'this', 'stuff']
Word: great
Replacement: awesome
['You', 'are', 'doing', 'awesome', 'with', 'this', 'stuff']
Word: stuff
Replacement: class
['You', 'are', 'doing', 'awesome', 'with', 'this', 'class']
Word:
You are doing awesome with this class

Notice how we have put an extra print() statement in the code so you can see how the list is being modified each time through the loop.

Scoreboard

We are going to write a game for two teams that has these rules:

  • the computer picks a secret number between 1 and 10
  • both teams try to guess the secret number
  • the team that is closer to the secret gets a point
  • if both teams are equally close, they both get a point
  • the first team to five points wins

To represent points, we are going to use two emojis:

POINT = '🟩'
NO_POINT = '⬜️'

To represent how many points each team has earned so far, we are going to use a list. This list means a team has scored zero points so far:

['⬜️', '⬜️', '⬜️', '⬜️', '⬜️']

ad this list means a team has scored three points so far:

['🟩', '🟩', '🟩', '⬜️', '⬜️']

Here is a flow chart for this problem:

scoreboard problem flow chart

We can also write this in English:

  • create score lists for each team
  • while True
    • pick a secret number
    • get guesses for each team
    • add a point for the team that wins (or both if there is a tie)
    • if Team 1 has five points
      • Team 1 wins!
      • break
    • if Team 2 has five points
      • Team 2 wins!
      • break

(You may notice a small issue with this code. If both teams make it to five points simultaneously, Team 1 wins. With some extra if statements you could fix this.)

Here is the code that implements these ideas, which you can find in scoreboard.py:

import random

# these are two variables that represent a point and no point
# since the variable names are in uppercase, we know we should
# never change them
POINT = '🟩'
NO_POINT = '⬜️'


def add_point(scores: list[str]):
    """
    <scores> is a list of POINT and NO_POINT characters

    Change the first NO_POINT in the list to POINT

    Modifies the <scores> list.
    """
    # loop through all of the characters in <scores>
    for i in range(len(scores)):
        # if this character is NO_POINT, change it to
        # POINT and return
        if scores[i] == NO_POINT:
            scores[i] = POINT
            return


def main():
    # create a list of 5 NO_POINT characters, representing
    # give empty
    # each list will look like this:
    # ['⬜️', '⬜️', '⬜️', '⬜️', '⬜️']
    team1_score = [NO_POINT] * 5
    team2_score = [NO_POINT] * 5

    while True:
        # choose a random number between 1 and 10
        secret = random.randint(1, 10)
        # decide which team goes first using a random number
        # between 0 and 1. 50% of the time team 1 will go first,
        # and 50% of teh time team 2 will go first
        if random.random() < 0.5:
            team1_guess = int(input('Team 1 guess: '))
            team2_guess = int(input('Team 2 guess: '))
        else:
            team2_guess = int(input('Team 2 guess: '))
            team1_guess = int(input('Team 1 guess: '))

        print(f"The secret number was {secret}")

        # calculate how far off each team was
        diff1 = abs(team1_guess - secret)
        diff2 = abs(team2_guess - secret)

        if diff1 < diff2:
            # if team 1 was closer, they get a point
            add_point(team1_score)
        elif diff2 < diff1:
            # if team 2 was closer, they get a point
            add_point(team2_score)
        else:
            # if both teams were equally close, they both get a point
            add_point(team1_score)
            add_point(team2_score)

        # print the scores
        print(f'Team 1: {team1_score}')
        print(f'Team 2: {team2_score}')

        # first team to five points wins
        # we can check if a team has five points by checking if
        # the last character in the list is equal to POINT
        if team1_score[-1] == POINT:
            print('Team 1 wins!')
            break

        if team2_score[-1] == POINT:
            print('Team 2 wins!')
            break


if __name__ == '__main__':
    main()

If you run this code, you should see something like this:

Team 2 guess: 5
Team 1 guess: 4
The secret number was 8
Team 1: ['⬜️', '⬜️', '⬜️', '⬜️', '⬜️']
Team 2: ['🟩', '⬜️', '⬜️', '⬜️', '⬜️']
Team 2 guess: 7
Team 1 guess: 6
The secret number was 9
Team 1: ['⬜️', '⬜️', '⬜️', '⬜️', '⬜️']
Team 2: ['🟩', '🟩', '⬜️', '⬜️', '⬜️']
Team 1 guess: 3
Team 2 guess: 4
The secret number was 5
Team 1: ['⬜️', '⬜️', '⬜️', '⬜️', '⬜️']
Team 2: ['🟩', '🟩', '🟩', '⬜️', '⬜️']
Team 2 guess: 8
Team 1 guess: 7
The secret number was 10
Team 1: ['⬜️', '⬜️', '⬜️', '⬜️', '⬜️']
Team 2: ['🟩', '🟩', '🟩', '🟩', '⬜️']
Team 2 guess: 5
Team 1 guess: 6
The secret number was 6
Team 1: ['🟩', '⬜️', '⬜️', '⬜️', '⬜️']
Team 2: ['🟩', '🟩', '🟩', '🟩', '⬜️']
Team 1 guess: 5
Team 2 guess: 4
The secret number was 5
Team 1: ['🟩', '🟩', '⬜️', '⬜️', '⬜️']
Team 2: ['🟩', '🟩', '🟩', '🟩', '⬜️']
Team 2 guess: 5
Team 1 guess: 6
The secret number was 10
Team 1: ['🟩', '🟩', '🟩', '⬜️', '⬜️']
Team 2: ['🟩', '🟩', '🟩', '🟩', '⬜️']
Team 2 guess: 3
Team 1 guess: 4
The secret number was 1
Team 1: ['🟩', '🟩', '🟩', '⬜️', '⬜️']
Team 2: ['🟩', '🟩', '🟩', '🟩', '🟩']
Team 2 wins!