← Back to the Introduction to Computer Science outline The Course Maker
← Introduction to Computer Science outline
Week 7 · Chapter

Chapter 7 — Functions

Introduction to Computer Science Free sample edition

The module's primary reading · ~40–50 minutes · Objective 5 (define and call functions with parameters, arguments, and return values, distinguish return from print, and reason correctly about local and global scope) and Objective 4 (write while loops and for loops over range(), using counters, accumulators, nested loops, and break, and recognize and repair an infinite loop and an off-by-one error) · Read it first this week; the lecture, the slides, the tutorial, the quiz, and the Coding Lab all build on it.


In plain language

So far every program you wrote ran from top to bottom, one line after another, and if you needed the same work twice you typed it twice. This week you learn to give a piece of work a name and run it whenever you want. A function (a named block of code that runs when you call it) is written once and reused as often as you like. You will hand a function values to work on, get a value back from it, and learn where the names inside a function live and when they disappear. As always, you predict what each program prints and then run it to check.

Why you already care about this

A coffee shop keeps one recipe card for an oat latte. The barista does not rewrite the recipe every time someone orders one; the card says "two shots, steamed oat milk, a pump of vanilla," and the barista follows it for order 1, order 2, and order 40. The card is written once. Each order hands it something different, such as a size, and each order produces a drink.

Programs need recipe cards too. Your reading log has computed "pages left" since Week 2, with the line pages_left = goal - pages_read. If you want that answer for a second book, or for a new goal, you would have to copy the line and change the names. A function lets you write the rule once, give it a name, and call it with new numbers each time. That keeps a program short, and when the rule has a bug you fix it in one place.

This chapter shows you how to define a function with def, how to hand it values through parameters, how to get a value back with return, why return and print are different, and where a function's names live. It ends with the three bugs nearly every new programmer writes, and how to find each one by running the code.

By the end of this chapter you will be able to:

  • define a function with def and say why defining it prints nothing;
  • tell a parameter from an argument and predict how argument order binds values;
  • use a returned value by storing it, printing it, or putting it in an expression;
  • predict the None that a printing function hands back;
  • say which names a function can read, which it creates, and when they vanish;
  • find and fix a forgotten return, a value printed instead of returned, and a local used outside its function.

Carry this line with you: print shows a value to you; return hands it back to the code.

Where we are

  • Already covered: Week 1 taught you to predict, run, and read an error from its last line; Week 2 gave you variables and arithmetic, including //; Week 3 gave you strings and f-strings; Week 4 taught conditions; Weeks 5 and 6 taught while and for loops, counters, and accumulators.
  • This week: defining and calling functions, parameters and arguments, return and None, local and global scope, and the three function bugs.
  • How they connect: a function packages the expressions, conditions, and loops you already write into one named piece of work, so you can reuse it with new values.
  • Where we're going next: Week 8 is the Midterm Review and Exam, a checkpoint on Weeks 1 to 7, and Week 9 uses functions on lists.

Three earlier ideas carry most of the weight. From Week 2, = puts a value into a name, and 255 // 30 is 8 because floor division drops the remainder. From Week 1, an error's last line names its type, such as NameError for a name Python does not know. From Weeks 5 and 6, a loop's body is the indented block under its header line; a function's body works the same way. Spend extra time on Sections 4 and 5. The midterm next week leans on both, and so does every week after it.

1. Define once, call by name: def runs nothing until you call

A bus driver memorizes one route. Every morning the dispatcher says "run Route 9," and the driver follows the same streets in the same order. Saying "run Route 9" is short. Learning the route happened once, long before.

A function is a named block of code that does one piece of work; it runs only when a line of the program calls it by name, and it can be called as many times as you need.

You write a function with def: the keyword that starts a function definition, written as def, then the function's name, then its parameters in parentheses and a colon, with the function's body indented below.

A function call is a line that runs a function by writing its name followed by parentheses holding the values to hand in, such as pages_left(300, 45); the body runs once for each call.

Worked example — defining is not running. Predict the first line of output before you read it.

def greet():
    print("Hello from inside the function")

print("Defined, not yet called")
greet()
greet()

Output

Defined, not yet called
Hello from inside the function
Hello from inside the function

Here is the trace, line by line.

  1. Python reads the def block and remembers the name greet and its body. It prints nothing.
  2. The first unindented line after the block prints Defined, not yet called.
  3. The line greet() is a call. Python jumps into the body, runs its one line, and comes back.
  4. The second greet() runs the body again, so the greeting prints a second time.

In words: the def block taught Python a new name, and only the two calls ran the body. One definition, two calls, two greetings.

Here is the same idea in a campus-club setting, an illustrative program that prints a divider line between sections of meeting notes.

def print_divider():
    print("==========")

print("Club meeting notes")
print_divider()
print("Snacks: fruit and crackers")
print_divider()

Output

Club meeting notes
==========
Snacks: fruit and crackers
==========

The divider rule is written once and used twice. To change every divider, you would edit one line inside the function.

Misconception check. Students often think that defining a function runs it, so they expect the greeting to print before Defined, not yet called. That is tempting, because the def block sits at the top of the file and Python runs files from top to bottom. But a def block only stores the body under a name. The cure: look for the call, the name followed by parentheses, and expect the body to run only there, once per call.

In practice (your tools). Paste the greet program into Python Tutor at pythontutor.com and click Next one step at a time. Watch the arrow skip over the body on the first pass, and watch greet appear in the global names panel as a function. On each call, the arrow jumps into the body and then returns. The Python Tutorial's section 4.8, Defining Functions, on docs.python.org, is the official description.

Memory hook: def stores the recipe; the call cooks it.

2. Hand values in: parameters and arguments

A vending machine has two slots: one takes the item code and one takes the coins. The slots are fixed parts of the machine. What you put into them changes every time. A function has slots too, and the values you drop in change with each call.

A parameter is a name written inside the parentheses of a def line; when the function is called, it receives the value supplied in the matching position of the call.

The values you write inside the call's parentheses are its arguments, the word Week 1 used for the values handed to print. Parameters live in the def line; arguments live in the call.

Worked example — the reading log as a function. The reading log's goal is 300 pages, and 45 pages were read when the program started. This week's stage packages the subtraction as a function. Predict all three lines.

def pages_left(goal, pages_read):
    return goal - pages_read

print(pages_left(300, 45))
print(pages_left(300, 300))
print(pages_left(200, 45) // 30)

Output

255
0
5

Here is how each call binds its arguments.

  1. pages_left(300, 45) binds goal to 300 and pages_read to 45, because they match by position: first to first, second to second. The body computes 300 − 45 = 255.
  2. pages_left(300, 300) binds both parameters to 300, and 300 − 300 = 0. The goal is met.
  3. pages_left(200, 45) computes 200 − 45 = 155. Section 3 explains the // 30 part.

The line return goal - pages_read hands the difference back to the call; Section 3 is about that keyword.

Worked example — order matters. Swap the two arguments and predict the result.

def pages_left(goal, pages_read):
    return goal - pages_read

print(pages_left(45, 300))

Output

-255

The call binds goal to 45 and pages_read to 300, so the body computes 45 − 300 = −255. Python did not complain. Both values were numbers, so the subtraction ran, and the program printed a wrong answer. That makes it a logic error.

Here is an illustrative bike-share message with two parameters of different kinds, a name and a dock number.

def dock_message(name, dock):
    print(f"{name}, your bike is at dock {dock}")

dock_message("Amara", 4)
dock_message(4, "Amara")

Output

Amara, your bike is at dock 4
4, your bike is at dock Amara

The second call put the arguments in the wrong order, so name got 4 and dock got "Amara". The f-string printed exactly what it was handed.

Misconception check. Students often think argument order does not matter, because Python can "see" which value is the goal. That is tempting, because a person reading pages_left(45, 300) knows that 300 is the goal. But Python matches arguments to parameters by position only, first to first and second to second. The cure: read the def line, then line up each argument with the parameter in the same position before you predict.

Notice the two words as well. Students often use "parameter" and "argument" for the same thing. They are different: goal is a parameter, a name in the def line, and 300 is an argument, the value in the call.

In practice (your tools). Paste the reading-log function into Python Tutor and step into the first call. A new box labelled pages_left appears, holding goal 300 and pages_read 45. Step into the swapped call and watch the same box show goal 45 instead.

Memory hook: parameters are the slots; arguments fill them in order.

3. Send a value back: return, and three ways to use it

A calculator does the arithmetic and then shows the result on its display, ready for your next step. You might write the number down, read it aloud, or add something to it. A function can hand back a result the same way.

return is the keyword that ends a function's call and hands the value written after it back to the line that made the call, where that value can be stored, printed, or used in an expression.

A return value is the value a function call hands back to the code that called it; it is the value of the call expression itself, so print(pages_left(300, 45)) prints it.

A call with a return value behaves like the value. You can use it in three ways.

Worked example — store it, print it, use it. Predict both lines.

def pages_left(goal, pages_read):
    return goal - pages_read

left = pages_left(300, 45)
print(left)
print(left // 30)

Output

255
8
  1. Store it: left = pages_left(300, 45) puts the returned 255 into the name left.
  2. Print it: print(left) prints 255.
  3. Use it in an expression: left // 30 is the same floor division as Week 2, 255 // 30, which is 8 full days of reading at 30 pages a day.

The pinned third line from Section 2 uses the call directly inside an expression. pages_left(200, 45) returns 155, and 155 // 30 is 5, because 5 × 30 = 150 fits in 155 and 6 × 30 = 180 does not. So a 200-page goal leaves 5 full days of reading.

Worked example — a temperature converter. Here is an illustrative converter from Celsius to Fahrenheit.

def to_fahrenheit(celsius):
    return celsius * 9 / 5 + 32

print(to_fahrenheit(20))
print(to_fahrenheit(0))

Output

68.0
32.0

For 20 degrees: 20 × 9 = 180, then 180 ÷ 5 = 36, then 36 + 32 = 68. The output shows 68.0, not 68, because / always gives a float, as Week 2 showed. For 0 degrees, the first two steps give 0, and 0 + 32 is 32.0.

Worked example — a receipt. Here is an illustrative receipt function that multiplies a price by a quantity. Two calls are added together in one expression.

def line_total(price, quantity):
    return price * quantity

print(line_total(4, 3))
print(line_total(4, 3) + line_total(2, 5))

Output

12
22

The first call returns 4 × 3 = 12. In the second line, each call returns its own value first: 12, then 2 × 5 = 10. Only then does + add them, and 12 + 10 = 22. In words: two returned values took part in one expression, the same way two numbers would.

Misconception check. Students often think a returned value appears on the output by itself. That is tempting, because the calls in Section 2 were wrapped in print, so their values showed up. But return only hands the value to the line that called; nothing prints unless that line prints it. A line that says pages_left(300, 45) on its own computes 255 and throws it away. The cure: decide what the calling line does with the value. Store it, print it, or use it.

In practice (your tools). In your free browser Python runner, type the receipt program and press Run. Then add a third call, line_total(3, 6), to the sum and predict the new total before you run it. In Python Tutor, step through one call and look for the label Return value in the function's box right before the box disappears.

Memory hook: a call with return acts like the value it hands back.

4. Tell return from print: the None a printing function hands back

A friend who whispers a phone number to you is different from a friend who writes it on a card and hands it over. You heard the whispered number once. The card is something you can keep, copy, and dial later. print whispers; return hands over the card.

None is Python's special value meaning no value at all; a call whose function reaches the end of its body without a return line hands back None.

Worked example — the pinned printing function. Predict both lines. Many people predict only one.

def show(x):
    print(x * 2)

print(show(4))

Output

8
None

Here is the trace.

  1. The outer print needs a value, so Python first runs the call show(4).
  2. Inside the call, x is 4, and print(x * 2) prints 8. That is the first line.
  3. The body ends with no return line, so the call hands back None.
  4. The outer print now prints the value it was handed, None. That is the second line.

In words: show printed its answer but returned nothing, so the code that called it received None.

Here is an illustrative tip calculator in a café setting that makes the same choice.

def add_tip(bill):
    print(bill + 3)

total = add_tip(20)
print(total)

Output

23
None

The number 23 printed from inside the function, and total received None. Change one word, and the function hands the number back instead.

def add_tip(bill):
    return bill + 3

total = add_tip(20)
print(total)

Output

23

Now 23 prints once, from the last line, and total holds 23, ready for more arithmetic.

Misconception check. Students often think print and return do the same thing, because both make a value "come out" of the function. That is tempting, because when you run a small program, both seem to put a number on the output. But print sends text to the output and hands nothing back, while return hands a value to the code and sends nothing to the output. The cure: ask "does any code need this value later?" If it does, the function must return it.

In practice (your tools). Paste the show program into Python Tutor at pythontutor.com and step until the call's box shows Return value None. The Python Tutorial's section 4.8 on docs.python.org notes that a function with no return still returns a value, None.

Memory hook: no return means None.

5. Read scope: local names, global names, and the frame

A hotel gives each guest a room safe. You can put things into your safe while you stay, but when you check out, the safe is emptied for the next guest. You can still see the lobby's posted notices from inside your room, and nothing you write on your own notepad changes them.

A local variable is a name created inside a function, including each parameter; it exists only while that call runs and is gone once the call ends.

A global variable is a name created at the top level of a program, outside every function; code inside a function can read it, but a plain assignment inside the function makes a separate local name instead of changing it.

Scope is the part of a program in which a name can be used; a local variable's scope is the one call that made it, while a global variable's scope is the whole program.

A frame is the box of local names that Python sets up when a function is called and removes when that call returns; Python Tutor draws one beside the program's global names for each running call.

Worked example — the scope program. This program has one global, level, and two functions. Predict all three lines.

level = 100

def show():
    print("inside sees", level)

def reset():
    level = 0
    print("inside", level)

show()
reset()
print("outside", level)

Output

inside sees 100
inside 0
outside 100

Here is what each frame holds.

  1. show() makes a frame with no local names. It looks for level, finds none in its frame, and reads the global: inside sees 100.
  2. reset() makes a frame, and level = 0 creates a new local level in that frame. It prints inside 0. The global was never touched.
  3. When reset() returns, its frame and its local level disappear.
  4. The last line runs at the top level, so it reads the global: outside 100.

In words: a function can read a global, but a plain assignment inside a function makes a local with the same name, and that local vanishes with its frame.

Worked example — the pinned local. Predict the single line.

n = 5

def change():
    n = 99

change()
print(n)

Output

5

The call made a local n in its own frame, set it to 99, and then the frame disappeared. The global n stayed 5 the whole time.

Misconception check. Students often think a variable set inside a function changes the outer one with the same name, so they predict 99. That is tempting, because the two names are spelled exactly alike. But each call gets its own frame, and a plain assignment inside the function writes into that frame. The cure: when a name is assigned inside a function, picture a new box in the function's frame, separate from the global box.

Honesty note: Python does have a keyword, global, that lets a function change a global name. It comes later, and well-written functions rarely need it, because returning a value does the same job in a way that is easier to follow.

In practice (your tools). Paste the scope program into Python Tutor at pythontutor.com and step through it. When reset() is called, a frame labelled reset appears below the global frame, holding its own level of 0. Click Next once more and the frame vanishes, while the global level still shows 100. This picture, a frame appearing and disappearing, is the picture of scope.

Memory hook: a call gets a fresh frame; the frame leaves when the call does.

6. Find and fix the three function bugs

A mechanic who knows the three most common reasons a car will not start checks those three first. Most function bugs are one of three slips, and each has a recognizable sign when you run the program.

Worked example — bug 1, a forgotten return. This version of the reading log computes the difference but never hands it back.

def pages_left(goal, pages_read):
    left = goal - pages_read

print(pages_left(300, 45))

Output

None

The sign is None where a number should be. The body ran and computed 255 into a local, but with no return line the call handed back None. The fix is one line, return left, at the end of the body:

def pages_left(goal, pages_read):
    left = goal - pages_read
    return left

print(pages_left(300, 45))

Output

255

Worked example — bug 2, printed instead of returned. This function prints its answer, and the calling code then tries to do arithmetic with it.

def double(x):
    print(x * 2)

y = double(4)
print(y + 1)

Output

8
TypeError

The sign is a value that printed, followed by an error. The call printed 8 and handed back None, so y holds None. Then None + 1 meets the wrong type, and Python raises TypeError; the last line of the error message names that type. The fix is to return the value instead of printing it:

def double(x):
    return x * 2

y = double(4)
print(y + 1)

Output

9

Now y holds 8, and 8 + 1 = 9.

Worked example — bug 3, a local used outside. This program calls the function and then tries to read the function's local name.

def pages_left(goal, pages_read):
    left = goal - pages_read
    return left

pages_left(300, 45)
print(left)

Output

NameError

The sign is a NameError for a name you can see in the function. The local left lived in the call's frame and vanished when the call returned. The top level never had a name left. Notice that nothing printed, because the error stopped the program before print could show anything. The fix is to store the returned value in a name of your own:

def pages_left(goal, pages_read):
    left = goal - pages_read
    return left

left = pages_left(300, 45)
print(left)

Output

255

The left on the last two lines is a global, filled by the return value. It happens to share a spelling with the local, but it is a separate box.

The three function bugs, the sign each one shows when you run it, and the one-line fix
Bug What the run shows The fix
Forgot to return None where a value should be Add a return line
Printed instead of returned The value, then TypeError when it is used Change print to return
Used a local outside NameError Store the returned value in a name

Misconception check. Students often think None or NameError means the function is broken inside, so they rewrite the arithmetic. That is tempting, because the error appears near the function. But in all three bugs the arithmetic was right. The problem was how the value left the function, or did not. The cure: before you touch the body, check the last line of the function for return and check how the calling line uses the value.

Each fix above is one line. The computer did exactly what you wrote, not what you meant, and the run told you where the gap was.

In practice (your tools). In your browser runner, type bug 2 and press Run. Read the error from the bottom up: the last line begins with TypeError. Then make the one-line fix and run it again. Keep a note of the three signs; next week's midterm review returns to them.

Memory hook: None, TypeError, NameError: check how the value leaves the function.

7. Check a chatbot's claim by running it

A friend tells you the shop on the corner closes at nine. You can trust it, or you can read the sign on the door. A chatbot's explanation of code is like the friend's claim, and the run is the sign on the door.

Worked example — a chatbot's claim about show. Suppose you ask a chatbot what print(show(4)) prints, and it answers: "show returns 8, so the program prints 8." Run the pinned program and compare.

def show(x):
    print(x * 2)

print(show(4))

Output

8
None

The run printed two lines, and the second is None. The chatbot treated a printing function as if it returned the printed value, which is the exact slip from Section 4. If you want the chatbot's claim to be true, the function must return:

def show(x):
    return x * 2

print(show(4))

Output

8

In words: the run caught a claim that sounded reasonable, and the cure was the one-word change from Section 4.

Misconception check. Students often think a chatbot's explanation must be right if the first line of output matches. That is tempting, because the 8 did appear. But a chatbot predicts likely text; it does not run your program, and it can miss a second line entirely. The cure: run the code, compare every line of output with the claim, and trust the run.

In practice (your tools). Ask your approved chatbot to write a function that converts kilometers to meters and to tell you what it returns for 3. Before you run its code, write down your own prediction. Then run it in your browser runner and check whether the function returns the value or only prints it, by putting the call inside print and looking for a None. The Python Tutorial's section 4.8 on docs.python.org is the place to confirm how return works.

Caveat: this chapter covers only the function tools Week 7 needs. Functions can also take default values for their parameters, accept arguments by name, change globals with the global keyword, and even call themselves. All of those come later.

Pause and choose one of these to think about before you read on:

  • (a) Think of a task you repeat with small changes, such as splitting a bill or converting a recipe for more people. What would you name the function, what would its parameters be, and what would it return?
  • (b) Think of a time a value seemed to vanish in a program you ran, or a total came out as None. Which of the three bugs from Section 6 was it most likely to be, and what one-line fix would you try first?

Memory hook: trust the run, not the explanation.

Key terms

  • function — A named block of code that does one piece of work; it runs only when a line of the program calls it by name, and it can be called as many times as you need. (first used: 1. Define once, call by name: def runs nothing until you call)
  • def — The keyword that starts a function definition, written as def, then the function's name, then its parameters in parentheses and a colon, with the function's body indented below. (first used: 1. Define once, call by name: def runs nothing until you call)
  • function call — A line that runs a function by writing its name followed by parentheses holding the values to hand in, such as pages_left(300, 45); the body runs once for each call. (first used: 1. Define once, call by name: def runs nothing until you call)
  • parameter — A name written inside the parentheses of a def line; when the function is called, it receives the value supplied in the matching position of the call. (first used: 2. Hand values in: parameters and arguments)
  • return — The keyword that ends a function's call and hands the value written after it back to the line that made the call, where that value can be stored, printed, or used in an expression. (first used: 3. Send a value back: return, and three ways to use it)
  • return value — The value a function call hands back to the code that called it; it is the value of the call expression itself, so print(pages_left(300, 45)) prints it. (first used: 3. Send a value back: return, and three ways to use it)
  • None — Python's special value meaning no value at all; a call whose function reaches the end of its body without a return line hands back None. (first used: 4. Tell return from print: the None a printing function hands back)
  • local variable — A name created inside a function, including each parameter; it exists only while that call runs and is gone once the call ends. (first used: 5. Read scope: local names, global names, and the frame)
  • global variable — A name created at the top level of a program, outside every function; code inside a function can read it, but a plain assignment inside the function makes a separate local name instead of changing it. (first used: 5. Read scope: local names, global names, and the frame)
  • scope — The part of a program in which a name can be used; a local variable's scope is the one call that made it, while a global variable's scope is the whole program. (first used: 5. Read scope: local names, global names, and the frame)
  • frame — The box of local names that Python sets up when a function is called and removes when that call returns; Python Tutor draws one beside the program's global names for each running call. (first used: 5. Read scope: local names, global names, and the frame)

Summary

  • A function is defined once with def and runs only when called.
  • Parameters are names in the def line; arguments are values in the call.
  • Arguments bind to parameters by position, so order matters.
  • return hands a value back; store it, print it, or use it.
  • print shows a value; it does not hand it back.
  • A function with no return hands back None.
  • A local lives in its call's frame and vanishes when the call ends.
  • An assignment inside a function makes a local, not a change to the global.

Check your understanding

Work these before looking at the answers — they are ungraded, and the answers (with reasoning) are at the bottom of the page.

  1. A step-counter app in an illustrative example defines steps_to_goal(goal, walked) with return goal - walked. Predict what print(steps_to_goal(8000, 6500)) prints, and then what print(steps_to_goal(6500, 8000)) prints.
  2. A dice game in an illustrative example defines roll_total(first, second) with print(first + second) as its only body line. The program runs total = roll_total(4, 5) and then print(total). Predict both lines of output.
  3. A theater program in an illustrative example sets tickets = 10 at the top level. A function sell() sets tickets = 3 and prints "inside" with tickets. The program calls sell() and then prints "outside" with tickets. Predict both lines.
  4. A team app in an illustrative example defines cheer() with print("Go team") as its body, then prints "Start", then runs for i in range(3): with cheer() in the loop body. How many lines print, and what is the first one?

Answers (worked reasoning — read after attempting)

  1. The first call binds goal to 8000 and walked to 6500, so it returns 8000 − 6500 = 1500, and 1500 prints. The second call swaps the arguments, so goal is 6500 and walked is 8000, and it returns 6500 − 8000 = −1500. Arguments bind by position, so swapping them flips the sign.
  2. The output is 9 and then None. The call prints 4 + 5 = 9 from inside the function, but the body has no return line, so the call hands back None and total holds None. This is the print-instead-of-return trap.
  3. The output is inside 3 and then outside 10. The assignment inside sell() makes a local tickets in the call's frame, so the function prints 3. When the call returns, that frame vanishes, and the top level still sees the global, 10.
  4. Four lines print, and the first is Start. The def block prints nothing on its own. Then Start prints, and the loop over range(3) makes 3 passes, each calling cheer() once, so Go team prints 3 times: 1 + 3 = 4 lines.

Next up in this module: the Readings & Resources page, the lecture tutorial, the practice set, and the quiz — and next week, the midterm review turns this week's three bug signs into one rule of the classic-bug checklist: no return means None.

Built with The Course Maker · samples.thecoursemaker.com