{"id":"more-lists-function-bugs","slug":"function-bugs","title":"Three bugs that hide in functions","text":"Functions that go through a list have a few classic bugs. None of them gives an error message: the function runs and returns a wrong answer, which makes them hard to spot. It pays to know them on sight.\n\n### Bug 1: using a variable from outside the function\n\nA function can see the variables of the main program, and using one of them by accident gives a function that only seems to work:\n\n```python\ndef print_items(items: list):\n    for item in shopping:\n        print(item)\n\n\nshopping = [\"milk\", \"eggs\"]\nprint_items(shopping)\nprint_items([\"tea\", \"rice\"])\n```\n\n```output\nmilk\neggs\nmilk\neggs\n```\n\nThe loop goes through `shopping`, not the parameter `items`, so the function prints the same list whatever you give it. It gets worse: in your programs, the lines that try out a function go under `if __name__ == \"__main__\":`, and the checks on this site call your function without running that block. Then `shopping` doesn't exist at all, and the function stops with a `NameError`. Inside a function, use only its parameters and the variables it creates itself.\n\n### Bug 2: overwriting a parameter\n\nThis function should count the scores that are at least the given limit:\n\n```python\ndef count_at_least(scores: list, score: int) -> int:\n    count = 0\n    for score in scores:\n        if score >= score:\n            count += 1\n    return count\n\n\nprint(count_at_least([3, 8, 5, 10], 6))\n```\n\n```output\n4\n```\n\nIt should print 2 (for 8 and 10). The loop variable has the same name as the second parameter, so the first round of the loop replaces the limit 6 with the score 3. From then on, `score >= score` compares a number with itself, which is always true. Give every variable a name of its own:\n\n```python\ndef count_at_least(scores: list, limit: int) -> int:\n    count = 0\n    for score in scores:\n        if score >= limit:\n            count += 1\n    return count\n```\n\nThe same happens when a helper list inside the function gets the parameter's name, as in `scores = []`: the list the function was given is gone.\n\n### Bug 3: returning too early\n\n`return` ends the function at once, even in the middle of a loop. This function should tell whether a list has a negative number in it:\n\n```python\ndef has_negative(numbers: list) -> bool:\n    for number in numbers:\n        if number < 0:\n            return True\n        else:\n            return False\n\n\nprint(has_negative([4, -2, 7]))\n```\n\n```output\nFalse\n```\n\nThe first number, 4, isn't negative, so the `else` branch returns `False` before the loop ever reaches -2. One negative number is enough to answer yes, but to answer no, the function has to check every number. So `return False` belongs after the loop:\n\n```python\ndef has_negative(numbers: list) -> bool:\n    for number in numbers:\n        if number < 0:\n            return True\n    return False\n\n\nprint(has_negative([4, -2, 7]))\nprint(has_negative([4, 2, 7]))\n```\n\n```output\nTrue\nFalse\n```\n","task":"`all_even(numbers)` should return `True` when every number in the list is even, and `False` otherwise. It returns too early: `all_even([2, 4, 5])` gives `True`. Fix it.\n","kind":"code","seed":"def all_even(numbers: list) -> bool:\n    for number in numbers:\n        if number % 2 == 0:\n            return True\n        else:\n            return False\n\n\nif __name__ == \"__main__\":\n    print(all_even([2, 4, 6]))\n    print(all_even([2, 4, 5]))\n","solution":"def all_even(numbers: list) -> bool:\n    for number in numbers:\n        if number % 2 != 0:\n            return False\n    return True\n\n\nif __name__ == \"__main__\":\n    print(all_even([2, 4, 6]))\n    print(all_even([2, 4, 5]))\n","hints":["Which answer can the function give as soon as it finds one number, and which answer needs every number checked?","One odd number is enough to return `False`, so return `False` inside the loop when a number is odd (`number % 2 != 0`).","`return True` goes after the loop, at the same indentation as `for`: if the loop ends, no number was odd."],"tests":[{"name":"all_even([2, 4, 5])","stdin":"","call":"all_even([2, 4, 5])\n","expect":"False","hidden":false,"returns":true},{"name":"all_even([2, 4, 6])","stdin":"","call":"all_even([2, 4, 6])\n","expect":"True","hidden":false,"returns":true},{"name":"all_even([6, 3, 4])","stdin":"","call":"all_even([6, 3, 4])\n","expect":"False","hidden":true,"returns":true},{"name":"all_even([8])","stdin":"","call":"all_even([8])\n","expect":"True","hidden":true,"returns":true},{"name":"all_even([1, 2])","stdin":"","call":"all_even([1, 2])\n","expect":"False","hidden":true,"returns":true}],"require":[],"forbid":[],"more":[{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"def count_word(words: list, word: str) -> int:\n    count = 0\n    for word in words:\n        if word == word:\n            count += 1\n    return count\n\n\nprint(count_word([\"cat\", \"dog\", \"cat\"], \"cat\"))\nprint(count_word([\"cat\", \"dog\", \"cat\"], \"fish\"))\n","solution":"def count_word(words: list, word: str) -> int:\n    count = 0\n    for word in words:\n        if word == word:\n            count += 1\n    return count\n\n\nprint(count_word([\"cat\", \"dog\", \"cat\"], \"cat\"))\nprint(count_word([\"cat\", \"dog\", \"cat\"], \"fish\"))\n","hints":["The loop variable has the same name as the second parameter. What happens to the word that was given?","On every round, `word` is the current word of the list, so `word == word` compares it with itself.","The condition is true on every round, so the function counts every word in the list."],"tests":[{"name":"Output","stdin":"","expect":"3\n3","hidden":false}],"require":[],"forbid":[],"lines":["3","3"]},{"task":"Write a function `first_long_word(words, min_length)` that returns the first word in the list that has at least `min_length` characters. If no word is that long, it returns an empty string `\"\"`.\n\nFor example, `first_long_word([\"sun\", \"planet\", \"galaxy\"], 5)` returns `\"planet\"`, and `first_long_word([\"sun\", \"moon\"], 5)` returns `\"\"`.\n","kind":"code","seed":"def first_long_word(words: list, min_length: int) -> str:\n    pass\n\n\nif __name__ == \"__main__\":\n    print(first_long_word([\"sun\", \"planet\", \"galaxy\"], 5))\n    print(first_long_word([\"sun\", \"moon\"], 5))\n","solution":"def first_long_word(words: list, min_length: int) -> str:\n    for word in words:\n        if len(word) >= min_length:\n            return word\n    return \"\"\n\n\nif __name__ == \"__main__\":\n    print(first_long_word([\"sun\", \"planet\", \"galaxy\"], 5))\n    print(first_long_word([\"sun\", \"moon\"], 5))\n","hints":["Go through the words with a `for` loop. As soon as one is long enough, you have the answer, so you can return it right away.","The empty string is the answer only when no word was long enough, so `return \"\"` goes after the loop, not in an `else`.","Name the loop variable something other than the parameters, such as `word`."],"tests":[{"name":"first_long_word([\"sun\", \"planet\", \"galaxy\"], 5)","stdin":"","call":"first_long_word([\"sun\", \"planet\", \"galaxy\"], 5)\n","expect":"'planet'","hidden":false,"returns":true},{"name":"first_long_word([\"sun\", \"moon\"], 5)","stdin":"","call":"first_long_word([\"sun\", \"moon\"], 5)\n","expect":"''","hidden":false,"returns":true},{"name":"first_long_word([\"comet\", \"orbit\", \"nebula\"], 5)","stdin":"","call":"first_long_word([\"comet\", \"orbit\", \"nebula\"], 5)\n","expect":"'comet'","hidden":true,"returns":true},{"name":"first_long_word([\"a\", \"bb\", \"ccc\", \"dddd\"], 4)","stdin":"","call":"first_long_word([\"a\", \"bb\", \"ccc\", \"dddd\"], 4)\n","expect":"'dddd'","hidden":true,"returns":true},{"name":"first_long_word([], 1)","stdin":"","call":"first_long_word([], 1)\n","expect":"''","hidden":true,"returns":true}],"require":[],"forbid":[]}]}