Building with small functions
First a quick recap of two words you'll meet all the time. In a function's definition, the names in the parentheses are its parameters. In a call, the values in the parentheses are the arguments:
def seat(name, row, number):
print(f"{name}: row {row}, seat {number}")
seat("Ada", 4, 12)
guest = "Linus"
seat(guest, 2 + 1, 7)
Ada: row 4, seat 12
Linus: row 3, seat 7When a function is called, the value of each argument is assigned to the parameter in the same position, for that call only. An argument can be a variable or a calculation: only its value is passed on, so the variable guest becomes the parameter name, and 2 + 1 becomes row.
Since only the positions count, arguments in the wrong order go to the wrong parameters, and Python doesn't notice: seat(12, 4, "Ada") happily prints 12: row 4, seat Ada. Python complains only about a wrong number of arguments, with a TypeError: with too few, it names the parameter that got no value; with too many, it says how many arguments the function takes and how many it was given. Check the order of the arguments yourself.
Small functions inside bigger ones
In the last module you saw that a function can call another one. That's the key to writing larger programs: write a small function, try it until it works, and then build a bigger one on top of it. Here price_line writes one line of a receipt, and receipt calls it for every line it needs:
def price_line(item, price):
print(f"{item}: {price} euros")
def receipt(first, first_price, second, second_price):
print("RECEIPT")
price_line(first, first_price)
price_line(second, second_price)
price_line("Total", first_price + second_price)
receipt("Coffee", 3, "Bun", 2)
print()
receipt("Tea", 2, "Soup", 7)
RECEIPT
Coffee: 3 euros
Bun: 2 euros
Total: 5 euros
RECEIPT
Tea: 2 euros
Soup: 7 euros
Total: 9 eurosreceipt doesn't need to know how a line looks: it only decides which lines there are. To write EUR instead of euros, you would change one line in price_line, and every receipt would follow. Look at what receipt passes on, too: its own parameters, a string of its own ("Total") and a calculation.
An argument doesn't have to come from the caller's parameters at all. Here bar draws one progress bar, and loading calls it in a loop, with the number of filled cells taken from a counter:
def bar(filled, size):
print("[" + "#" * filled + "-" * (size - filled) + "]")
def loading(size):
done = 0
while done <= size:
bar(done, size)
done += 1
loading(4)
[----]
[#---]
[##--]
[###-]
[####]Each piece stays short and easy to check, and once bar works, every function built on it draws its bars right. When a bigger function goes wrong, try the smaller ones on their own in the main block first: that tells you which piece has the bug.
The same idea helps when a task looks too big to start: look for the small function hidden inside it. Write that one first and run it with a call or two. Only when it prints what you expect, write the function that uses it.