Python Arena
Step 5 of 5

Passing data between functions

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.

In 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:

def read_points() -> list:
    points = []
    while True:
        line = input("Points: ")
        if line == "":
            break
        points.append(int(line))
    return points


def passed(points: list) -> int:
    count = 0
    for p in points:
        if p >= 50:
            count += 1
    return count


def report(points: list) -> str:
    average = sum(points) / len(points)
    passed_count = passed(points)
    return f"{len(points)} exams, average {average:.1f}, {passed_count} passed"


def main():
    points = read_points()
    print(report(points))


main()
Input
72
45
90
(an empty line)
Output
Points: 72
Points: 45
Points: 90
Points:
3 exams, average 69.0, 2 passed

Follow 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.)

A trap: variables from outside

A 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:

def with_tax(price: float) -> float:
    return price * (1 + tax_rate)


tax_rate = 0.5
print(with_tax(10))
tax_rate = 0.25
print(with_tax(10))
Output
15.0
12.5

The 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.

In 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:

def with_tax(price: float) -> float:
    return price * (1 + tax_rate)


print(with_tax(10))
tax_rate = 0.5
Python stops with
Traceback (most recent call last):
  File "main.py", line 5, in <module>
    print(with_tax(10))
          ~~~~~~~~^^^^
  File "main.py", line 2, in with_tax
    return price * (1 + tax_rate)
                        ^^^^^^^^
NameError: name 'tax_rate' is not defined

The 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.

Your task

Challenge 1 of 3

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.

Challenge 2

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:

  • read_steps() reads the counts until an empty line and returns them in a list.
  • 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.
  • best_day(steps) returns the number of the day with the most steps (the first one if several are equal).
Steps: 8500
Steps: 12000
Steps: 4300
Steps:
Day 1:   8500 ########
Day 2:  12000 ############
Day 3:   4300 ####
Total: 24800
Average: 8266.7
Best day: 2

You can assume at least one count is typed.

Challenge 3

What does this program print? Type each line it prints.