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()
72
45
90
(an empty line)Points: 72
Points: 45
Points: 90
Points:
3 exams, average 69.0, 2 passedFollow 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))
15.0
12.5The 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
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 definedThe 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.