Functions

Every program so far has been one list of instructions, run from the top. A function is a group of instructions with a name. You write it once, and from then on the name is an instruction of your own, as usable as circle or print.

Before this, you can: write a loop that uses its loop variable; write an if with and / or; tell what a program prints by tracing it.

By the end you will be able to define a function and call it, pass values in and hand a value back, tell showing a value from returning it, say which names live inside a function, build one function out of others, and write a function that runs when the picture is clicked.

Before you read on, decide what this prints:

def total(a, b):
    a + b

print(total(1, 2))
None

If you expected 3, you may know a language where the last value worked out in a function is what it gives back. In Python a function gives back only what a return line hands over. This one works out 3 and then drops it, so the call gives back None, Python's word for "no value". Nothing warns you. This module comes back to that twice, because it is one of the most common mistakes in Python.

Your own instruction

Here is a function and two calls of it:

def cheer():
    print("Hip hip")
    print("Hooray")

cheer()
cheer()
Hip hip
Hooray
Hip hip
Hooray

The line starting with def gives the function its name. The indented lines under it are its body: what the function does. cheer() further down is a call: it runs the body, and when the body is finished the program carries on from the line after the call.

The round brackets matter. cheer is the name of the function; cheer() runs it.

Define is not do

A def does not run the body. It only teaches Python the name. Nothing in the body happens until a call.

def cheer():
    print("Hip hip")
    print("Hooray")

print("Ready")
cheer()
Ready
Hip hip
Hooray

Ready comes first, although the two print lines of the body are higher up the page. Python read the def, remembered the body, and moved on. A program with a def and no call of it prints nothing from that function at all.

One consequence: a function has to be defined before the line that calls it is run. Put your def lines near the top.

Values in

A function is more useful when each call can be different. The names in the brackets of the def are parameters. A call puts a value into each one, in order.

from scimigo import canvas, circle, rectangle

canvas(600, 400)

def draw_tree(x, height):
    rectangle(x - 8, 320 - height, 16, height, color="saddlebrown")
    circle(x, 320 - height, 40, color="forestgreen")

draw_tree(120, 100)
draw_tree(300, 180)
draw_tree(480, 60)

In the first call x is 120 and height is 100. In the second they are 300 and 180. The body is the same three times; only the values differ.

You have been doing the other half of this since the first module. circle(x, 200, 40, color="forestgreen") is a call that passes values in. A value can also be passed by the parameter's name, as color= is, and then the order does not matter:

def frame(width, height):
    return width - height

print(frame(5, 2))
print(frame(height=2, width=5))
3
3

Python has no declared types for parameters. draw_tree("tall", 100) is accepted as a call and fails only when the body tries to subtract 8 from "tall".

A value out

print shows a value to the person at the screen. return hands a value back to the program, at the place where the function was called.

def double(n):
    return n * 2

print(double(4) + 1)
print(double(double(3)))
9
12

Read double(4) + 1 as: run double with n = 4, and put what it returns, 8, in place of the call. Then 8 + 1 is 9. In the second line the inner call returns 6, and that 6 is what the outer call receives.

return also ends the function at once. Lines after it in the body do not run:

def entry_fee(age):
    if age < 5:
        return 0
    print("checking", age)
    return 12

print(entry_fee(3))
print(entry_fee(30))
0
checking 30
12

For entry_fee(3) the function stops at return 0, so checking is never printed. That makes a rule with several cases easy to write: return as soon as you know the answer.

Showing is not returning

These two functions look alike and are not.

def area_shown(w, h):
    print(w * h)

def area(w, h):
    return w * h

first = area_shown(2, 3)
second = area(2, 3)
print(first, second)
6
None 6

area_shown puts 6 on the screen and hands nothing back, so first is None. area shows nothing and hands 6 back, so second is 6. The program can go on to use second. It cannot use first as an area, to add it or multiply it.

A function that reaches the end of its body without a return gives back None. That is not an error by itself. The error comes later, when the program tries to use the None as a number:

def area_shown(w, h):
    print(w * h)

total = area_shown(2, 3) + area_shown(1, 4)
  File "your_program.py", line 4
    total = area_shown(2, 3) + area_shown(1, 4)
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

Both calls run and show their numbers, and then the + fails. Whenever an error mentions NoneType, look for a function that shows its answer, or works it out, and never returns it.

Which should a function do? If the rest of the program needs the value, return it. Let the main program decide whether to print it.

Names that stay inside

A name that is given a value inside a function belongs to that call of the function. It appears when the call starts and is gone when the call ends.

x = 10

def change():
    x = 99
    print("inside", x)

change()
print("outside", x)
inside 99
outside 10

There are two different names here that happen to be spelt the same. The x inside change is made by the call and thrown away after it. The x outside is never touched.

Parameters are names inside the function too. Giving a parameter a new value changes nothing outside:

def add_one(n):
    n = n + 1

score = 5
add_one(score)
print(score)
5

The call copies the value 5 into n. The function changes its own n to 6 and then ends, and that n is gone. score was never involved. To get the new value out, the function has to return it and the caller has to keep it: score = add_one(score), with return n + 1 in the body.

When you step through a program in the lab, each call gets its own box of names, which appears at the call and disappears at the return. Watch it once and this section becomes obvious.

Small functions make big ones

A function can call another function. That is how large programs are built: small pieces with clear names, used by slightly bigger pieces.

def tax(price):
    return price // 5

def price_with_tax(price):
    extra = tax(price)
    return price + extra

print(price_with_tax(50))
60

Follow the one call on the last line. price_with_tax starts with price = 50. Its first line calls tax, so price_with_tax waits while tax runs with its own price, returns 10, and ends. Now extra is 10, and price_with_tax returns 60 to the last line, which prints it.

Each call has its own names. The two functions both have a price, and they do not interfere.

The same idea draws pictures. A function that draws one tree can be used by a function that draws a row of them:

from scimigo import canvas, circle, rectangle

canvas(600, 400)

def draw_tree(x, height):
    rectangle(x - 8, 320 - height, 16, height, color="saddlebrown")
    circle(x, 320 - height, 40, color="forestgreen")

def draw_row(count, height):
    for i in range(count):
        draw_tree(70 + i * 115, height)

draw_row(5, 140)

draw_row knows nothing about rectangles and circles. It only knows that draw_tree draws a tree.

A function you never call yourself

So far your program has made every call. A function can also be handed to something else, which calls it later. on_click takes a function and remembers it. Each time the picture is clicked, the lab calls that function with the position of the click.

from scimigo import canvas, circle, on_click

canvas(600, 400)

def clicked(x, y):
    circle(x, y, 10, color="blue")

on_click(clicked)
print("waiting for a click")
waiting for a click

Notice on_click(clicked), with no brackets after clicked. You are handing over the function itself, not calling it. on_click(clicked()) would call clicked straight away, with no position, and fail.

When this program is run it draws nothing: the body of clicked has not run, because nothing has called it. Then you click at, say, (120, 80), and the lab calls clicked(120, 80). A second click calls it again with new values. The function has to take exactly two parameters, because two values are passed in.

In the lab

You will put the lines of a program in the order they run, finish a flower function and plant a garden with it, make a function return a width, decide when the last line of a function runs, tell showing from returning, follow one function calling another, and then write functions that the picture calls when you click it.