Python Arena
Step 3 of 6

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)
Output
Ada: row 4, seat 12
Linus: row 3, seat 7

When 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)
Output
RECEIPT
Coffee: 3 euros
Bun: 2 euros
Total: 5 euros

RECEIPT
Tea: 2 euros
Soup: 7 euros
Total: 9 euros

receipt 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)
Output
[----]
[#---]
[##--]
[###-]
[####]

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.

Your task

Challenge 1 of 3

Fill in the blanks so that battery_drain(size) shows a battery running empty: it calls bar with all size cells filled first, then one fewer each time, down to none. battery_drain(3) prints:

[###]
[##-]
[#--]
[---]

Challenge 2

Write the function frame(width, height), which draws a hollow box of # characters: a full row at the top and at the bottom, and between them rows with a # at each end and spaces in the middle. The box is width characters wide and height rows tall (both at least 2). Use the two functions in the starter code to draw the rows. frame(5, 4) prints:

#####
#   #
#   #
#####

Challenge 3

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