{"id":"errors-passing","slug":"passing-errors","title":"How errors move from function to function","text":"When an exception happens inside a function and the function doesn't handle it, the function stops right there, and the exception moves on to the code that called the function. If that code doesn't handle it either, it moves on to that code's caller, and so on. If nothing on the way handles it, the program stops, and the traceback lists every call on the way:\n\n```python\ndef average(numbers):\n    return sum(numbers) / len(numbers)\n\n\ndef report(name, numbers):\n    print(name, \"average:\", average(numbers))\n\n\nreport(\"Week 1\", [3, 5, 7])\nreport(\"Week 2\", [])\n```\n\n```output\nWeek 1 average: 5.0\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 10, in <module>\n    report(\"Week 2\", [])\n    ~~~~~~^^^^^^^^^^^^^^\n  File \"main.py\", line 6, in report\n    print(name, \"average:\", average(numbers))\n                            ~~~~~~~^^^^^^^^^\n  File \"main.py\", line 2, in average\n    return sum(numbers) / len(numbers)\n           ~~~~~~~~~~~~~^~~~~~~~~~~~~~\nZeroDivisionError: division by zero\n```\n\nRead it from the bottom up: the error happened in `average` on line 2, which was called by `report` on line 6, which was called by the main program on line 10. The line that's really wrong is often one of the callers: here it's the main program, which asked for the average of an empty list.\n\n### Handling it where you know what to do\n\nSo an exception can be handled in a different place from where it happens, and often the right place is the caller. A function that reads a file can't know whether a missing file is a disaster or just means there's no data yet, but the code that calls it does:\n\n```python\ndef read_numbers(filename):\n    numbers = []\n    with open(filename) as file:\n        for line in file:\n            numbers.append(int(line))\n    return numbers\n\n\ntry:\n    savings = read_numbers(\"savings.txt\")\nexcept FileNotFoundError:\n    savings = []\nprint(\"Savings:\", savings)\n```\n\n```output\nSavings: []\n```\n\nThere's no `savings.txt`, so `open` raises a `FileNotFoundError` inside `read_numbers`. The function stops at once (it never gets to its `return`), and the exception arrives at the `try` around the call, whose `except` decides what to do.\n\n### What doesn't run\n\nWhen an exception leaves a `try` block, the rest of the block is skipped, including any later calls:\n\n```python\ndef check(value):\n    print(\"Checking\", value)\n    number = int(value)\n    print(\"Valid:\", number)\n    return number\n\n\ntry:\n    check(\"12\")\n    check(\"twelve\")\n    check(\"7\")\nexcept ValueError:\n    print(\"Stopped at an invalid value\")\nprint(\"Done\")\n```\n\n```output\nChecking 12\nValid: 12\nChecking twelve\nStopped at an invalid value\nDone\n```\n\n`check(\"twelve\")` stops at `int`, so it never prints `Valid:`, and `check(\"7\")` is never called at all. To check every value whatever happens, the `try` would go inside a loop, around each call.\n","task":"The function `load_scores(filename)` reads whole numbers from a file. Add error handling to the main program, not to the function: a missing file prints `Could not find ` and the file name, and a line that isn't a whole number prints `Bad data in ` and the file name. A run with a good file looks like this:\n\n```\nScores file: scores.txt\nBest score: 87\n```\n","kind":"code","seed":"def load_scores(filename):\n    scores = []\n    with open(filename) as file:\n        for line in file:\n            scores.append(int(line))\n    return scores\n\n\nfilename = input(\"Scores file: \")\nscores = load_scores(filename)\nprint(\"Best score:\", max(scores))\n","solution":"def load_scores(filename):\n    scores = []\n    with open(filename) as file:\n        for line in file:\n            scores.append(int(line))\n    return scores\n\n\nfilename = input(\"Scores file: \")\ntry:\n    scores = load_scores(filename)\n    print(\"Best score:\", max(scores))\nexcept FileNotFoundError:\n    print(\"Could not find\", filename)\nexcept ValueError:\n    print(\"Bad data in\", filename)\n","hints":["The exceptions happen inside `load_scores`, but they move on to the line that calls it. Put that call in a `try` block.","Put the `print` in the `try` block too, since it needs `scores`. Then add `except FileNotFoundError:` and `except ValueError:` blocks."],"tests":[{"name":"Test 1","stdin":"scores.txt\n","files":{"scores.txt":"72\n87\n65\n"},"expect":"Scores file: scores.txt\nBest score: 87","hidden":false},{"name":"Test 2","stdin":"score.txt\n","files":{"scores.txt":"72\n87\n"},"expect":"Scores file: score.txt\nCould not find score.txt","hidden":true},{"name":"Test 3","stdin":"round2.txt\n","files":{"round2.txt":"50\nfifty\n"},"expect":"Scores file: round2.txt\nBad data in round2.txt","hidden":true}],"require":[{"pattern":"except\\s+FileNotFoundError","message":"Handle a missing file with `except FileNotFoundError:`."},{"pattern":"except\\s+ValueError","message":"Handle a line that isn't a number with `except ValueError:`."}],"forbid":[],"more":[{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"def half(text):\n    return int(text) // 2\n\n\nfor text in [\"8\", \"x\", \"5\"]:\n    try:\n        print(half(text))\n    except ValueError:\n        print(\"skip\", text)\nprint(\"end\")\n","solution":"def half(text):\n    return int(text) // 2\n\n\nfor text in [\"8\", \"x\", \"5\"]:\n    try:\n        print(half(text))\n    except ValueError:\n        print(\"skip\", text)\nprint(\"end\")\n","hints":["The `ValueError` from `int` inside `half` moves on to the `try` around the call.","The `try` is inside the loop, so after `x` is skipped, the loop goes on with `5`. `5 // 2` is 2."],"tests":[{"name":"Output","stdin":"","expect":"4\nskip x\n2\nend","hidden":false}],"require":[],"forbid":[],"lines":["4","skip x","2","end"]},{"task":"The starter code has `read_numbers(filename)`, which returns the numbers of a file as a list and raises a `FileNotFoundError` if the file doesn't exist. Write `total_of_files(filenames)`, which gets a list of file names and returns the sum of all the numbers in all of them. A file that doesn't exist counts as 0. Handle the error in `total_of_files`, and don't change `read_numbers`.\n\nWith `a.txt` holding `3` and `4` on two lines and `b.txt` holding `10`, `total_of_files([\"a.txt\", \"b.txt\", \"c.txt\"])` returns `17`.\n","kind":"code","seed":"def read_numbers(filename):\n    numbers = []\n    with open(filename) as file:\n        for line in file:\n            numbers.append(int(line))\n    return numbers\n\n\ndef total_of_files(filenames):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(total_of_files([\"a.txt\", \"b.txt\", \"c.txt\"]))\n","solution":"def read_numbers(filename):\n    numbers = []\n    with open(filename) as file:\n        for line in file:\n            numbers.append(int(line))\n    return numbers\n\n\ndef total_of_files(filenames):\n    total = 0\n    for filename in filenames:\n        try:\n            total += sum(read_numbers(filename))\n        except FileNotFoundError:\n            pass\n    return total\n\n\nif __name__ == \"__main__\":\n    print(total_of_files([\"a.txt\", \"b.txt\", \"c.txt\"]))\n","hints":["Go through the file names with a loop, and add `sum(read_numbers(filename))` to a total.","The `try` goes inside the loop, around the call, so a missing file skips only that one file.","There's nothing to do for a missing file, so its `except FileNotFoundError:` block has only `pass`."],"tests":[{"name":"total_of_files([\"a.txt\", \"b.txt\", \"c.txt\"])","stdin":"","call":"total_of_files([\"a.txt\", \"b.txt\", \"c.txt\"])\n","files":{"a.txt":"3\n4\n","b.txt":"10\n"},"expect":"17","hidden":false,"returns":true},{"name":"total_of_files([\"x.txt\", \"jan.txt\", \"feb.txt\"])","stdin":"","call":"total_of_files([\"x.txt\", \"jan.txt\", \"feb.txt\"])\n","files":{"jan.txt":"100\n-20\n","feb.txt":"5\n"},"expect":"85","hidden":true,"returns":true},{"name":"total_of_files([\"none.txt\"])","stdin":"","call":"total_of_files([\"none.txt\"])\n","expect":"0","hidden":true,"returns":true}],"require":[],"forbid":[]}]}