{"id":"references-side-effects","slug":"side-effects","title":"Side effects","text":"A function that changes something outside itself, besides returning a value, has a **side effect**. When that is its job, as with `add_bonus` in the previous step, the side effect is what you want. When it happens by accident, it's a bug, and a sneaky one: the function returns the right answer, and the damage shows up somewhere else in the program.\n\nThis function works out what an order costs, with the delivery fee added:\n\n```python\ndef order_total(prices: list, delivery: int) -> int:\n    prices.append(delivery)\n    return sum(prices)\n\n\nbasket = [12, 30, 8]\nprint(\"To pay:\", order_total(basket, 5))\nprint(\"Basket:\", basket)\n```\n\n```output\nTo pay: 55\nBasket: [12, 30, 8, 5]\n```\n\nThe total is right, but `append` added the fee to the caller's list, and now the basket holds a 5 that the customer never chose. The function doesn't need to change the list at all to find its answer:\n\n```python\ndef order_total(prices: list, delivery: int) -> int:\n    return sum(prices) + delivery\n\n\nbasket = [12, 30, 8]\nprint(\"To pay:\", order_total(basket, 5))\nprint(\"Basket:\", basket)\n```\n\n```output\nTo pay: 55\nBasket: [12, 30, 8]\n```\n\nWhen a function does need to change the items to find its answer, to sort them or to take some out, it should change a copy, not the list it was given.\n\n### The same call, a different answer\n\nA side effect can also make a function give a different answer each time it's called:\n\n```python\ndef sum_except_first(numbers: list) -> int:\n    numbers.pop(0)\n    return sum(numbers)\n\n\nbills = [100, 20, 30, 40]\nprint(sum_except_first(bills))\nprint(sum_except_first(bills))\n```\n\n```output\n90\n70\n```\n\nEach call removes an item from `bills` for good. A slice is a new list, so `return sum(numbers[1:])` gives the same answer without touching the caller's list.\n\n### Pure functions\n\nA function whose only result is the value it returns, and which changes nothing else, is called a **pure function**. Pure functions are easier to trust and to test: the same arguments always give the same answer, and calling one can't break anything elsewhere. A good habit:\n\n- A function that computes an answer should not change its arguments. If it needs to sort, remove or replace items, it works on a copy (`sorted`, `copy()`, a slice) or on a new list of its own.\n- A function whose job is to change a list says so in its description, and returns nothing.\n\nThe methods that change a list are the ones to watch in a function that computes an answer: `append`, `insert`, `remove`, `pop`, `sort`, and assigning to an index or a slice.\n","task":"In a diving contest, the judges' scores for a dive are in a list. The highest and the lowest score are dropped, and the dive gets the mean of the rest. `dive_score(scores)` returns the right score, but it also removes two scores from the caller's list. Fix it so it doesn't change the list it's given.\n\n```python\nscores = [7, 9, 8, 5, 9]\nprint(dive_score(scores))\nprint(scores)\n```\n\n```\n8.0\n[7, 9, 8, 5, 9]\n```\n","kind":"code","seed":"def dive_score(scores: list) -> float:\n    scores.remove(max(scores))\n    scores.remove(min(scores))\n    return sum(scores) / len(scores)\n\n\nif __name__ == \"__main__\":\n    scores = [7, 9, 8, 5, 9]\n    print(dive_score(scores))\n    print(scores)\n","solution":"def dive_score(scores: list) -> float:\n    kept = scores.copy()\n    kept.remove(max(kept))\n    kept.remove(min(kept))\n    return sum(kept) / len(kept)\n\n\nif __name__ == \"__main__\":\n    scores = [7, 9, 8, 5, 9]\n    print(dive_score(scores))\n    print(scores)\n","hints":["`remove` takes the item out of the very list the caller gave the function.","Make a copy first, `kept = scores.copy()`, and remove the highest and the lowest score from the copy.","Then compute the mean from the copy, and leave `scores` alone."],"tests":[{"name":"Test 1","stdin":"","call":"scores = [7, 9, 8, 5, 9]\nprint(dive_score(scores))\nprint(scores)\n","expect":"8.0\n[7, 9, 8, 5, 9]","hidden":false},{"name":"Test 2","stdin":"","call":"scores = [6, 10, 6, 2]\nprint(dive_score(scores))\nprint(scores)\n","expect":"6.0\n[6, 10, 6, 2]","hidden":true},{"name":"Test 3","stdin":"","call":"scores = [4, 8, 6]\nprint(dive_score(scores))\nprint(dive_score(scores))\nprint(scores)\n","expect":"6.0\n6.0\n[4, 8, 6]","hidden":true},{"name":"Test 4","stdin":"","call":"scores = [9.5, 7.0, 8.5, 8.0]\nprint(dive_score(scores))\nprint(scores)\n","expect":"8.25\n[9.5, 7.0, 8.5, 8.0]","hidden":true}],"require":[],"forbid":[],"more":[{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"def last_two(items: list) -> list:\n    result = []\n    result.append(items.pop())\n    result.append(items.pop())\n    return result\n\n\nletters = [\"a\", \"b\", \"c\", \"d\"]\nprint(last_two(letters))\nprint(letters)\nprint(last_two(letters))\n","solution":"def last_two(items: list) -> list:\n    result = []\n    result.append(items.pop())\n    result.append(items.pop())\n    return result\n\n\nletters = [\"a\", \"b\", \"c\", \"d\"]\nprint(last_two(letters))\nprint(letters)\nprint(last_two(letters))\n","hints":["`pop()` without an index removes the last item of the list and returns it.","`items` refers to the caller's list `letters`, so each call removes two items from `letters` for good.","The second call works on what's left after the first one."],"tests":[{"name":"Output","stdin":"","expect":"['d', 'c']\n['a', 'b']\n['b', 'a']","hidden":false}],"require":[],"forbid":[],"lines":["['d', 'c']","['a', 'b']","['b', 'a']"]},{"task":"Write a pure function `without(items, value)` that returns a new list with every item except the ones equal to `value`, in their original order. The list it's given must not change.\n\n```python\nitems = [1, 3, 1, 2]\nprint(without(items, 1))\nprint(items)\n```\n\n```\n[3, 2]\n[1, 3, 1, 2]\n```\n","kind":"code","seed":"def without(items: list, value) -> list:\n    pass\n\n\nif __name__ == \"__main__\":\n    items = [1, 3, 1, 2]\n    print(without(items, 1))\n    print(items)\n","solution":"def without(items: list, value) -> list:\n    result = []\n    for item in items:\n        if item != value:\n            result.append(item)\n    return result\n\n\nif __name__ == \"__main__\":\n    items = [1, 3, 1, 2]\n    print(without(items, 1))\n    print(items)\n","hints":["Don't use `remove` on `items`, because that changes the caller's list.","Start with a new empty list and append each item that isn't equal to `value`. Return the new list."],"tests":[{"name":"Test 1","stdin":"","call":"items = [1, 3, 1, 2]\nprint(without(items, 1))\nprint(items)\n","expect":"[3, 2]\n[1, 3, 1, 2]","hidden":false},{"name":"Test 2","stdin":"","call":"items = [\"red\", \"blue\", \"red\", \"green\"]\nprint(without(items, \"red\"))\nprint(items)\n","expect":"['blue', 'green']\n['red', 'blue', 'red', 'green']","hidden":true},{"name":"Test 3","stdin":"","call":"items = [7, 8]\nprint(without(items, 9))\nprint(items)\n","expect":"[7, 8]\n[7, 8]","hidden":true}],"require":[],"forbid":[]}]}