{"id":"strings-and-lists-larger-programs","slug":"larger-programs","title":"Passing data between functions","text":"Your programs are getting longer. You already know how to grow one: a small function at a time, each one run and checked before another function relies on it. (The step counter at the end of this step is meant to be written that way too.) This step is about what connects those functions: the data they share.\n\nIn a program made of functions, each function gets the data it needs as **arguments**, and gives the data it produces back with **`return`**. A `main` function ties them together. Here's a small program that reads exam points and reports on them:\n\n```python\ndef read_points() -> list:\n    points = []\n    while True:\n        line = input(\"Points: \")\n        if line == \"\":\n            break\n        points.append(int(line))\n    return points\n\n\ndef passed(points: list) -> int:\n    count = 0\n    for p in points:\n        if p >= 50:\n            count += 1\n    return count\n\n\ndef report(points: list) -> str:\n    average = sum(points) / len(points)\n    passed_count = passed(points)\n    return f\"{len(points)} exams, average {average:.1f}, {passed_count} passed\"\n\n\ndef main():\n    points = read_points()\n    print(report(points))\n\n\nmain()\n```\n\n```input\n72\n45\n90\n\n```\n\n```output\nPoints: 72\nPoints: 45\nPoints: 90\nPoints:\n3 exams, average 69.0, 2 passed\n```\n\nFollow the data: `read_points` returns the list, `main` stores it in its own variable and hands it to `report`, which hands it on to `passed`. No function reaches for a variable outside itself. Each function is easy to try out alone, too: `passed([50, 49])` should be 1, whatever else the program does. The program starts from the call `main()` on the last line. (A program like this, with no function task, can call `main` at the top level.)\n\n### A trap: variables from outside\n\nA function can read a variable that was made outside it, at the top level of the program. It works, which is exactly what makes it a trap. Here the tax rate isn't a parameter, so the function uses whatever value `tax_rate` has when it's called:\n\n```python\ndef with_tax(price: float) -> float:\n    return price * (1 + tax_rate)\n\n\ntax_rate = 0.5\nprint(with_tax(10))\ntax_rate = 0.25\nprint(with_tax(10))\n```\n\n```output\n15.0\n12.5\n```\n\nThe same call, `with_tax(10)`, gives two different answers, and nothing in the call shows why. In a long program, finding the line that changed `tax_rate` can take a long time. A loop that goes through an outside list instead of the list parameter goes wrong the same way.\n\nIn a function task, it's worse. The checks don't run the main block, so a variable set there has no value when they call your function. Calling the function before the variable is set shows what they see:\n\n```python\ndef with_tax(price: float) -> float:\n    return price * (1 + tax_rate)\n\n\nprint(with_tax(10))\ntax_rate = 0.5\n```\n\n```crash\nTraceback (most recent call last):\n  File \"main.py\", line 5, in <module>\n    print(with_tax(10))\n          ~~~~~~~~^^^^\n  File \"main.py\", line 2, in with_tax\n    return price * (1 + tax_rate)\n                        ^^^^^^^^\nNameError: name 'tax_rate' is not defined\n```\n\nThe cure is to give a function everything it needs as parameters: `def with_tax(price: float, tax_rate: float) -> float:`. Then every call shows all the data its answer depends on.\n","task":"`discounted` in the starter code uses the variable `discount` from the main block. It works when you run the program, but the checks fail with a `NameError`. Give the function a second parameter, `discount: int` (the discount in percent), and change the call in the main block to match. For example, `discounted(50, 20)` returns `40.0`.\n","kind":"code","seed":"def discounted(price: float) -> float:\n    return price * (100 - discount) / 100\n\n\nif __name__ == \"__main__\":\n    discount = 20\n    print(discounted(50))\n","solution":"def discounted(price: float, discount: int) -> float:\n    return price * (100 - discount) / 100\n\n\nif __name__ == \"__main__\":\n    print(discounted(50, 20))\n","hints":["The checks call `discounted(50, 20)`: the percentage comes as the second argument, so the function needs a second parameter to receive it.","Add `discount: int` after `price: float` in the first line. The body can stay as it is, because `discount` is now the parameter. Then give the call in the main block the percentage as its second argument."],"tests":[{"name":"discounted(50, 20)","stdin":"","call":"discounted(50, 20)\n","expect":"40.0","hidden":false,"returns":true},{"name":"discounted(80, 25)","stdin":"","call":"discounted(80, 25)\n","expect":"60.0","hidden":true,"returns":true},{"name":"discounted(9.5, 0)","stdin":"","call":"discounted(9.5, 0)\n","expect":"9.5","hidden":true,"returns":true},{"name":"discounted(30, 50)","stdin":"","call":"discounted(30, 50)\n","expect":"15.0","hidden":true,"returns":true}],"require":[],"forbid":[],"more":[{"task":"Finish a step counter program. It reads daily step counts until an empty line, prints a chart with one `#` for each full thousand steps, and then the total, the average and the best day. The `main` function is ready; write the three functions it uses:\n\n- `read_steps()` reads the counts until an empty line and returns them in a list.\n- `print_chart(steps)` prints a line for each day: `Day` and the day's number from 1, a colon and a space, the steps in a column of 6 characters, a space and the `#` characters.\n- `best_day(steps)` returns the number of the day with the most steps (the first one if several are equal).\n\n```\nSteps: 8500\nSteps: 12000\nSteps: 4300\nSteps:\nDay 1:   8500 ########\nDay 2:  12000 ############\nDay 3:   4300 ####\nTotal: 24800\nAverage: 8266.7\nBest day: 2\n```\n\nYou can assume at least one count is typed.\n","kind":"code","seed":"def read_steps() -> list:\n    pass\n\n\ndef print_chart(steps: list):\n    pass\n\n\ndef best_day(steps: list) -> int:\n    pass\n\n\ndef main():\n    steps = read_steps()\n    print_chart(steps)\n    print(\"Total:\", sum(steps))\n    print(f\"Average: {sum(steps) / len(steps):.1f}\")\n    print(\"Best day:\", best_day(steps))\n\n\nmain()\n","solution":"def read_steps() -> list:\n    steps = []\n    while True:\n        line = input(\"Steps: \")\n        if line == \"\":\n            break\n        steps.append(int(line))\n    return steps\n\n\ndef print_chart(steps: list):\n    for i in range(len(steps)):\n        print(f\"Day {i + 1}: {steps[i]:6} {'#' * (steps[i] // 1000)}\")\n\n\ndef best_day(steps: list) -> int:\n    best = 0\n    for i in range(len(steps)):\n        if steps[i] > steps[best]:\n            best = i\n    return best + 1\n\n\ndef main():\n    steps = read_steps()\n    print_chart(steps)\n    print(\"Total:\", sum(steps))\n    print(f\"Average: {sum(steps) / len(steps):.1f}\")\n    print(\"Best day:\", best_day(steps))\n\n\nmain()\n","hints":["Write and try one function at a time. `read_steps` is like reading names until an empty line, but it converts each line with `int` before appending it and returns the list.","In `print_chart`, go through the indexes. The number of `#` characters is `steps[i] // 1000`, and `\"#\" * n` makes n of them. In the f-string, write the `#` in single quotes: `{'#' * (steps[i] // 1000)}`.","In `best_day`, find the index of the greatest count, as you did when finding the best item of a list, and return that index plus 1."],"tests":[{"name":"Test 1","stdin":"8500\n12000\n4300\n\n","expect":"Steps: 8500\nSteps: 12000\nSteps: 4300\nSteps:\nDay 1:   8500 ########\nDay 2:  12000 ############\nDay 3:   4300 ####\nTotal: 24800\nAverage: 8266.7\nBest day: 2","hidden":false},{"name":"Test 2","stdin":"999\n1000\n15500\n15500\n\n","expect":"Steps: 999\nSteps: 1000\nSteps: 15500\nSteps: 15500\nSteps:\nDay 1:    999\nDay 2:   1000 #\nDay 3:  15500 ###############\nDay 4:  15500 ###############\nTotal: 32999\nAverage: 8249.8\nBest day: 3","hidden":true},{"name":"Test 3","stdin":"7000\n\n","expect":"Steps: 7000\nSteps:\nDay 1:   7000 #######\nTotal: 7000\nAverage: 7000.0\nBest day: 1","hidden":true}],"require":[],"forbid":[]},{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"def scaled(value):\n    return value * factor\n\n\nfactor = 2\nprint(scaled(5))\nfactor = 10\nprint(scaled(5))\nprint(scaled(factor))\n","solution":"def scaled(value):\n    return value * factor\n\n\nfactor = 2\nprint(scaled(5))\nfactor = 10\nprint(scaled(5))\nprint(scaled(factor))\n","hints":["`factor` isn't a parameter of `scaled`: the function uses the value the outside variable has at the moment of the call.","The first call happens while `factor` is 2. Before the second call, `factor` becomes 10.","In the last call, `factor` is both the argument (10) and the multiplier (10)."],"tests":[{"name":"Output","stdin":"","expect":"10\n50\n100","hidden":false}],"require":[],"forbid":[],"lines":["10","50","100"]}]}