{"id":"own-modules-type-hints","slug":"type-hints","title":"Type hints","text":"When you use a function from a module, you usually don't read its code. You read its first line, and that line should tell you what to pass and what you get back. That's what the **type hints** you met in part 4 are for, and with modules they earn their keep:\n\n```python\ndef with_tax(price: float, percent: int) -> float:\n    return price * (1 + percent / 100)\n```\n\n- `price: float` says the parameter `price` should be a float, and `percent: int` an integer.\n- `-> float`, between the parentheses and the colon, says what the function returns. A function that returns nothing has `-> None`.\n- A hint can also say what a list or a dictionary holds: `list[int]` is a list of integers, `dict[str, int]` a dictionary with string keys and integer values, and `tuple[int, int]` a tuple of two integers.\n\nHere is a module with hints, and a program that uses it:\n\n```python\nimport scores\n\npoints = [7, 9, 4, 10]\nprint(scores.best(points))\nprint(scores.passed(points, 5))\n# ==== scores.py ====\ndef best(points: list[int]) -> int:\n    return max(points)\n\n\ndef passed(points: list[int], limit: int) -> int:\n    count = 0\n    for p in points:\n        if p >= limit:\n            count += 1\n    return count\n```\n\n```output\n10\n3\n```\n\nReading only the first lines of `scores.py`, you know that both functions want a list of integers and return an integer, and that `passed` also wants a limit. Editors such as Visual Studio Code show these first lines when you type a module's name and a dot, so you don't even need to open the module's file. A module is written once and used by many programs, often by other people, so these few words in its first lines save a lot of reading.\n\n### When the hint is ignored\n\nPython itself doesn't check the hints, as you saw in part 4. A call with the wrong type runs, and goes wrong only where the value is used. With modules, that place is in another file, and the traceback has a line for each file on the way:\n\n```python\nimport money\n\nprint(money.with_tax(\"10\", 24))\n# ==== money.py ====\ndef with_tax(price: float, percent: int) -> float:\n    return price * (1 + percent / 100)\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 3, in <module>\n    print(money.with_tax(\"10\", 24))\n          ~~~~~~~~~~~~~~^^^^^^^^^^\n  File \"money.py\", line 2, in with_tax\n    return price * (1 + percent / 100)\n           ~~~~~~^~~~~~~~~~~~~~~~~~~~~\nTypeError: can't multiply sequence by non-int of type 'float'\n```\n\nRead it from the bottom: the error happened in `money.py`, on line 2, where a string met a multiplication. The line above names the call in `main.py` that got there. The hint `price: float` told the caller what to pass; the caller passed the string `\"10\"`. Separate checker programs (such as mypy) can find mistakes like this from the hints before the program runs, but Python doesn't.\n","task":"Add type hints to the function: its parameter is a list of floats, and it returns a float.\n","kind":"fill","seed":"def average(numbers: [[list]][[[float]]]) -> [[float]]:\n    return sum(numbers) / len(numbers)\n\n\nprint(average([2.5, 3.5, 4.0]))\n","solution":"def average(numbers: list[float]) -> float:\n    return sum(numbers) / len(numbers)\n\n\nprint(average([2.5, 3.5, 4.0]))\n","hints":["A hint for a list names two types: first the type of the container, then in square brackets the type of its items, as in `list[int]`.","Here the container is a `list` and its items are floats. The return type goes after the arrow `->`: the type of the average."],"tests":[{"name":"Output","stdin":"","expect":"3.3333333333333335","hidden":false}],"require":[{"pattern":"numbers:\\s*list\\[float\\]","message":"The parameter's hint should say a list of floats: `list[float]`."},{"pattern":"->\\s*float\\s*:","message":"The function returns a float, so its return hint is `-> float`."}],"forbid":[],"more":[{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"def count_letters(words: list[str]) -> dict[str, int]:\n    counts = {}\n    for word in words:\n        if word in counts:\n            counts[word] += 1\n        else:\n            counts[word] = 1\n    return counts\n\n\nprint(count_letters([\"a\", \"b\", \"a\"]))\nprint(count_letters(\"aba\"))\n","solution":"def count_letters(words: list[str]) -> dict[str, int]:\n    counts = {}\n    for word in words:\n        if word in counts:\n            counts[word] += 1\n        else:\n            counts[word] = 1\n    return counts\n\n\nprint(count_letters([\"a\", \"b\", \"a\"]))\nprint(count_letters(\"aba\"))\n","hints":["The first call matches the hints. The function counts how many times each item of the list comes up, in a dictionary.","Python doesn't check type hints, so the second call runs too, with a string.","A `for` loop over a string goes through its characters: `a`, `b` and `a`."],"tests":[{"name":"Output","stdin":"","expect":"{'a': 2, 'b': 1}\n{'a': 2, 'b': 1}","hidden":false}],"require":[],"forbid":[],"lines":["{'a': 2, 'b': 1}","{'a': 2, 'b': 1}"]},{"task":"`main.py` is ready, and `grades.py` has the first lines of two functions, with type hints. Write their bodies:\n\n- `grade(points: int) -> int` returns the grade for 0 to 100 points: below 50 is 0, then each 10 points up is a grade higher (50 to 59 is 1, 60 to 69 is 2, and so on), and 90 or more is 5.\n- `describe(grade: int) -> str` returns `\"failed\"` for grade 0, `\"passed\"` for grades 1 and 2, `\"good\"` for 3 and 4, and `\"excellent\"` for 5.\n\nFor example:\n\n```\nPoints: 85\nGrade 4: good\n```\n","kind":"code","seed":"# ==== main.py ====\nimport grades\n\npoints = int(input(\"Points: \"))\nresult = grades.grade(points)\nprint(f\"Grade {result}: {grades.describe(result)}\")\n\n# ==== grades.py ====\ndef grade(points: int) -> int:\n    pass\n\n\ndef describe(grade: int) -> str:\n    pass\n","solution":"# ==== main.py ====\nimport grades\n\npoints = int(input(\"Points: \"))\nresult = grades.grade(points)\nprint(f\"Grade {result}: {grades.describe(result)}\")\n\n# ==== grades.py ====\ndef grade(points: int) -> int:\n    if points < 50:\n        return 0\n    if points >= 90:\n        return 5\n    return (points - 40) // 10\n\n\ndef describe(grade: int) -> str:\n    if grade == 0:\n        return \"failed\"\n    if grade <= 2:\n        return \"passed\"\n    if grade <= 4:\n        return \"good\"\n    return \"excellent\"\n","hints":["The hints say what each function returns: `grade` an integer, and `describe` a string.","In `grade`, handle below 50 and 90 or more first. Between them, an `if` and `elif` chain works, and so does `(points - 40) // 10`.","In `describe`, compare the grade from the smallest up and return the matching word."],"tests":[{"name":"Test 1","stdin":"85\n","expect":"Points: 85\nGrade 4: good","hidden":false},{"name":"Test 2","stdin":"49\n","expect":"Points: 49\nGrade 0: failed","hidden":true},{"name":"Test 3","stdin":"50\n","expect":"Points: 50\nGrade 1: passed","hidden":true},{"name":"Test 4","stdin":"100\n","expect":"Points: 100\nGrade 5: excellent","hidden":true},{"name":"Test 5","stdin":"72\n","expect":"Points: 72\nGrade 3: good","hidden":true},{"name":"Test 6","stdin":"60\n","expect":"Points: 60\nGrade 2: passed","hidden":true}],"require":[],"forbid":[]}]}