Class 4 - Functions
Date: September 16, 2026
Skills: 0, 1
Notes (review before class)
Functions from Similar Expressions
These draw the flags of Armenia and Austria: the same shape, in different
colors (frame draws a thin outline around an image):
# Lines starting with # are comments, for human readers:
# Python ignores everything on a line after #.
# armenia
frame(above(rectangle(120, 30, "solid", "red"),
above(rectangle(120, 30, "solid", "blue"),
rectangle(120, 30, "solid", "orange"))))
# austria
frame(above(rectangle(120, 30, "solid", "red"),
above(rectangle(120, 30, "solid", "white"),
rectangle(120, 30, "solid", "red"))))
We'd like to write the shared part once and give just the colors, but Python has no such operation (it's specific to our program):
# armenia
three_stripe_flag("red", "blue", "orange")
# austria
three_stripe_flag("red", "white", "red")
A function is an operation we add ourselves: it takes parameters, and uses them to produce a result.
Defining Functions
To make a function from expressions that differ in only a few values:
-
Write down at least two examples (the two flags).
-
Find the fixed parts (
120by30rectangles, stacked withabove) and the changing parts (the colors). -
Name each changing part: these are the parameters (
top,middle,bottom). -
Rewrite the expression with those names in place of the changing parts.
-
Pick a suggestive name:
three_stripe_flag. -
Wrap it in the syntax for functions (
<...>means "fill in"):def <function name>(<parameters>):
return <the expression goes here>
def three_stripe_flag(top, middle, bottom):
return frame(above(rectangle(120, 30, "solid", top),
above(rectangle(120, 30, "solid", middle),
rectangle(120, 30, "solid", bottom))))
- The indented lines after
defare the function's body;returnsays what value it produces. - With practice, you won't need to start from several examples.
Giving values for the parameters is calling the function;
three_stripe_flag("red", "blue", "orange") is a call:
armenia = three_stripe_flag("red", "blue", "orange")
austria = three_stripe_flag("red", "white", "red")
- If your file already defines
armenia, PLL reports a reassignment: use another name, or comment out the earlier definition with#.
Whitespace Matters
Indentation (pushing lines in from the left edge) is how Python knows which
lines belong to the body: in Python, whitespace (spaces, tabs, new lines)
carries meaning, where many languages use braces or end:
def double(n):
return n * 2
def double(n):
return n * 2
- The second is an error: after the
defline, Python finds no indented body. - Indent by four spaces (the editor does this when you press Enter after
the
:). - Be consistent: lines in the same body need the same indentation. A tab (copied in from elsewhere) can look just like spaces, but isn't: let the editor insert spaces.
- For an indentation error, read down the left edge first: the shape of the code is wrong.
- This applies to everything with a body: conditionals, loops, and more.
How Functions Evaluate
- A function definition adds an entry to the directory, from the function's name to its code (and shows nothing). The body is not evaluated yet.
- A call evaluates the body, with the call's values substituted for the
parameters:
three_stripe_flag("red", "blue", "orange")becomes exactly the Armenia expression above, and evaluates to its flag. - Equivalently, the call adds
top,middle, andbottomto a local directory, used only while the body runs and then removed: they can't be used outside the function (more on Class 5).
Type Annotations and Docstrings
three_stripe_flag(50, "blue", "red") substitutes 50 for top: at best, we
get an error about rectangle (confusing: the caller wrote no rectangles); at
worst, a wrong picture and no error. The complete definition fixes this, and
explains the function:
def three_stripe_flag(top: str, middle: str, bottom: str) -> Image:
"""produce image of flag with three equal-height horizontal stripes"""
return frame(above(rectangle(120, 30, "solid", top),
above(rectangle(120, 30, "solid", middle),
rectangle(120, 30, "solid", bottom))))
- Type annotations: each parameter gets a colon and its type (so far:
int,float,str, orImage), and the result's type goes after->, as in the documentation's contracts. Now PLL reports thatthree_stripe_flagexpectstopto be a string. - Python runs functions without annotations, but we always write them: errors are reported more precisely, and readers know how to use the function.
- A docstring is a short, human-language description of what the function does (not necessarily how): a string with three double quotes on each side, on the first line of the body. A name can't say everything (stacked stripes? equal heights?), so always write one, for any reader, including you in a few weeks.
- The design recipe for functions starts with these. Step 1: the type annotations (work out the types first: everything else depends on them). Step 2: the docstring.
Example: Moon Weight
Objects on the Moon weigh one-sixth of their Earth weight. We expect to
compute this once per astronaut (100 * 1/6, 150 * 1/6, 90 * 1/6, ...),
and repeating it invites typos, so we write a function right away ("DRY":
"don't repeat yourself"), with fixed part * 1/6 and the Earth weight as
parameter:
def moon_weight(earth_weight: float) -> float:
"""Compute weight on moon from weight on earth"""
return earth_weight * 1/6
Example: Cost of Pens
Each pen costs 25 cents, plus 2 cents per character of the message printed on it (counting spaces):
# ordering 3 pens that say "wow"
3 * (0.25 + len("wow")*0.02)
# ordering 10 pens that say "smile"
10 * (0.25 + len("smile")*0.02)
def pen_cost(num_pens: int, message: str) -> float:
"""total cost for pens, each 25 cents
plus 2 cents per message character"""
return num_pens * (0.25 + len(message)*0.02)
lengives the number of characters in a string (including spaces and punctuation).- Two things change, so there are two parameters. The number of pens is
whole (
int); the cost has cents (float, written with a decimal point:0,02doesn't work). - A docstring can continue onto another line.
Recap (10 mins)
Every student joining the class gets the same greeting, except for their name. Read through the program:
# "Welcome to class, " + "Ada"
# "Welcome to class, " + "Grace"
def welcome(name: str) -> str:
"""greeting that welcomes name to class"""
return "Welcome to class, " + name
ada_greeting = welcome("Ada")
grace_greeting = welcome("Grace")
- Which part of the two commented-out expressions became the parameter? What
does the body become for
welcome("Grace"), and what isgrace_greeting? welcome(2000)is a mistake. Where does PLL report the problem? Where would it be reported if the first line were justdef welcome(name):?- After the file runs, what happens if you enter
nameat the prompt? Why?
Class Exercise (40 mins)
defwith cakes- Your goal is to draw an image of a three layer cake. Each cake consists of three equally sized rectangular layers, and the only difference is the color (or "flavor") of each layer. You can choose the colors!
- Now, draw another cake with different flavors (i.e., colors). Notice what is different, and what is the same.
- Define a new function,
three_layer_cakethat takes as arguments the three parts that change: the color of the top, middle, and bottom colors. - Be sure to give it a type annotation and docstring.
- Now use this new function to rewrite both of your past examples, and notice how much easier it is to read even though the behavior is the same: this is your second example of Design -- just like constant definitions can make expressions easier to read, by splitting large expressions into smaller parts or giving meaning to numbers, functions give names to repeated computations.
defwith t-shirt printing- Your next task is to write a function to calculate the cost of printing a t-shirt, which can
have a message written on it. Each shirt costs$5.00plus an extra$0.10per letter printed on it. - First, write an expression for 4 t-shirts with the message "Go Team!".
- Now make another expression, this time for 7 t-shirts with the message "Hello World".
- Are there numbers that should be definitions? So-called "magic numbers" that aren't obvious what they mean are always good candidates for creating definitions -- do that now (there should be two!).
- Now, look at the two expressions, and figure out what changes between the two,
and write a function,
tshirt_cost, that takes as arguments the parts that change. - Start with the type annotation -- think about what type the arguments should be. Next write down a docstring. Finally, write the expression for the body! Be sure to only include the part that changes, so that once you rewrite your original expressions to use your new function, the only repeated part is the name of the function!
- Your next task is to write a function to calculate the cost of printing a t-shirt, which can
Wrap-up (10 mins)
- Functions allow us to not repeat ourselves; more importantly, they are our first, and in this class, primary, example of a mechanism for abstraction -- i.e., building a re-usable piece of a program, that has internals that are not visible to other parts of the program. Abstraction is one of, if not the most, important ideas in computer science.
- Type annotations allow us to clarify and communicate what kind of input each parameter expects and the kind of output the function will return.
- Docstrings communicate what the function does in a clear, simple way.
- These serve both as a guide to others, but more importantly, as a guide to YOU! As thinking through the inputs / outputs before writing the function, and describing what the function should do, helps you figure out how to implement it.