Splitting a Script into Functions
A script that grew from top to bottom works, but it is hard to read, hard to change, and impossible to test piece by piece. Splitting it into functions, each doing one job, with a main function that calls them in order, turns it into something you can explain line by line to someone else, or to yourself in a month.
Think of a table of contents. It lists the chapters in order, and each chapter holds its own detail. main is the table of contents of your program, and each function is a chapter you open only when you need its detail.
Before: One Long Script
Here is the tip calculator from Chapters 2 and 4, as it stands: it asks for the bill, asks for the percentage until the answer is a whole number, and prints the tip and the total. It works. But every job is mixed in with every other, and nothing has a name that says what it is for.
from decimal import Decimal bill = Decimal(input("Bill amount: ")) while True: answer = input("Tip percent: ") if answer.isdigit(): break print("Please type a whole number, like 15.") percent = int(answer) tip = bill * percent / 100 print(f"Tip: {tip:.2f}") print(f"Total: {bill + tip:.2f}")
One Job per Function
Split it by job. Asking for the bill is one job. Asking for a valid percentage is another. Working out the tip is a third, and a pure calculation: numbers in, number out. A simple test tells you whether a function does one job: if its honest name needs the word "and", it is doing two.
main Tells the Story
main calls the other functions in order and does nothing clever itself, so reading it tells you what the program does. The last line of the file calls main(). Chapter 9 adds one more line around that call, for a reason that only matters once a program is split across files.
Docstrings
A docstring is a string in triple quotes placed first in a function's body, describing what the function does. It is not a comment: Python keeps it with the function, and the built-in help shows it. Typing help(calculate_tip) at the prompt, after defining the function, shows its name, its parameters and this description, as below. Press q to leave the help view.
Help on function calculate_tip in module __main__:
calculate_tip(bill, percent)
Return the tip for a bill at the given percentage.
from decimal import Decimal def ask_bill(): """Ask for the bill and return it as a Decimal.""" return Decimal(input("Bill amount: ")) def ask_percent(): """Ask for a tip percentage until the answer is a whole number.""" while True: answer = input("Tip percent: ") if answer.isdigit(): return int(answer) print("Please type a whole number, like 15.") def calculate_tip(bill, percent): """Return the tip for a bill at the given percentage.""" return bill * percent / 100 def main(): bill = ask_bill() percent = ask_percent() tip = calculate_tip(bill, percent) print(f"Tip: {tip:.2f}") print(f"Total: {bill + tip:.2f}") main()
Bill amount: 48.50 Tip percent: fifteen Please type a whole number, like 15. Tip percent: 15 Tip: 7.28 Total: 55.78
Notice return inside the while loop of ask_percent. A return leaves the function at once, loop and all, so it does the job of break and hands back the answer in one step.
Before and After
The program behaves exactly as before; the run above shows the same answers as the original. What changed is how it reads. main is five lines that say what happens. Each job can be read on its own, and calculate_tip in particular now takes numbers and returns a number, with no typing involved, which is what Chapter 12 needs to test it automatically.
- "More functions make a program slower." The difference is too small to measure in scripts like these. The gain in clarity is large.
- "Every line deserves its own function." One job per function, and a job is usually several lines. A function per line is as hard to read as none.
- "Docstrings are just comments with a different quote." They are part of the function: Python keeps them, and help shows them.
- A script split into named jobs can be changed one job at a time without fear of breaking the rest.
- The same shape, small functions and a main, is how the complete program in Chapter 12 is built, and how most Python projects are organised.
Knowledge Check
What is a sign that a function is doing two jobs?
- It has more than two parameters in its brackets
- Its honest name needs the word "and"
- It contains a loop as well as an if statement
- It is longer than five lines of code
What is main for in this program?
- It holds all of the program's code, so nothing is split
- Python calls main automatically when the file runs, even without a call
- It calls the other functions in order, telling the program's story
- It stores the values the other functions need to share
Where does a docstring go?
- On the line just above the def line, starting with a hash sign
- At the very end of the file, after the call to main
- In a separate text file that sits right next to the program file
- As the first line of the function's body, in triple quotes
Why does return inside the while loop of ask_percent work without a break?
- return leaves the whole function at once, loop included
- return only stops the current round of the loop
- Python adds a break before every return automatically
- The loop ends because isdigit changes the answer to a number
You got correct