This lesson shows that variables and parameters inside a function are local and vanish when it returns, introduces the stack diagram and the traceback that mirrors it, separates fruitful functions from void ones, and gives the four reasons to divide a program into functions.
Subject: Python · 65 slides · code lesson
Open the interactive version of this deck
Title
Python · Chapter 3 — Functions
§3.8-3.12, pp. 22-25
Objectives
Five things, each one you can check yourself at an interpreter prompt.
Think Python, 2nd edition — Allen B. Downey §3.8-3.12, pp. 22-25 — the pages these objectives are drawn from
Warm-up
Lesson 3b left a question open. Answer it from what you already know.
Discussion prompt
In def print_twice(bruce), the name bruce exists while the function is running. Does it still exist after the function has finished? Give a reason for your answer before you find out.
Hint: Think about what would happen if you called the function twice with different arguments.
Answer:
It does not. When the function terminates, its variables are destroyed, and trying to use bruce outside gives a NameError.
The argument from calling it twice is the strong one: if bruce survived, the second call would have to overwrite the first call's value, and two calls could interfere with each other. Making the names local is what stops that.
This lesson makes that precise, draws the picture that shows it, and then shows that the picture is the same thing Python prints when something goes wrong.
Concept
When you create a variable inside a function, it is local, which means that it only exists inside the function. Parameters are local too. Every call brings its names into existence and takes them away again when it finishes.
local variable — A variable defined inside a function, which exists only while that function is running.
This is what makes functions independent of one another. A function can use whatever names it likes without any risk of clashing with names used elsewhere, because its names are not visible elsewhere.
Figure (svg): A stack diagram with a main frame containing line1 and line2, a cat_twice frame containing part1, part2 and cat, and a print_twice frame containing bruce
Think Python, 2nd edition — Allen B. Downey §3.8-3.12, pp. 22-23 — figure 3.1, the stack diagram
Section
Section 1
Concept
When you create a variable inside a function, it is local, which means it only exists inside the function. When the function terminates, the variable is destroyed.
def cat_twice(part1, part2):
cat = part1 + part2
print_twice(cat)| Name | Why it is local | Lifetime |
|---|---|---|
| part1, part2 | parameters — local | created by the call |
| cat | created inside the body — local | created by line 2 |
| after the call | all three destroyed | none exists outside |
This function takes two arguments, concatenates them, and prints the result twice. When cat_twice terminates, the variable cat is destroyed — and if you try to print it afterwards, you get an exception.
Think Python, 2nd edition — Allen B. Downey §3.8-3.12, pp. 22-23
Picture it
Before the call they do not exist. During it they do. After it they are gone.
Figure (svg): Two columns contrasting names that exist in the main program with names that exist only inside a function
Destroyed is the book's word and it is accurate: the name is gone, and so is any way of reaching the value through it.
Worked example
Predict what happens before advancing. The error is one you have seen before.
>>> line1 = 'Bing tiddle '
>>> line2 = 'tiddle bang.'
>>> cat_twice(line1, line2)
Bing tiddle tiddle bang.
Bing tiddle tiddle bang.
>>> print(cat)
NameError: name 'cat' is not defined| Moment | Does cat exist? | Result |
|---|---|---|
| during the call | cat exists inside cat_twice | 'Bing tiddle tiddle bang.' |
| at the return | cat is destroyed | gone |
| print(cat) | the name does not exist here | NameError |
Run the call and watch it work.
Why: The output appears, so cat certainly existed and held the concatenated string while the function was running.
Try to use it afterwards.
Why: When cat_twice terminates, the variable cat is destroyed. The name is not merely empty; it does not exist.
Read the error.
Why: NameError: name 'cat' is not defined. It is exactly the same error as using a name you never created, because from __main__'s point of view you never did.
Figure (svg): The state of the program after each line of Worked example a local variable used outside its function, drawn as a ladder with one rung per traced line
A NameError. The variable cat lived only inside cat_twice and was destroyed when the function returned, so from outside there is no such name.
Verify: Check the same claim for a parameter rather than a local variable.
Why: Printing bruce outside print_twice gives the same NameError. Parameters are also local — outside print_twice, there is no such thing as bruce — which confirms that the rule is about being inside a function rather than about how the name was created.
Prediction
The function has finished. Decide what survives.
def f(x):
y = x * 2
print(y)
z = 5
f(z)| Line | What it creates | After the call |
|---|---|---|
| z = 5 | created in __main__ | survives |
| f(z) | creates x and y inside f | both local |
| after f returns | x and y destroyed | only z remains |
Predict first
After the last line runs, which names still exist?
Correct: Only z — x and y were local to f and were destroyed when it returned.
Why: z was created by an assignment in the main program, so it belongs to __main__ and persists. x is a parameter and y is a variable created inside the body; both are local, both are created by the call, and both are destroyed when the function terminates. Trying to use either afterwards gives a NameError.
Worked example
This is what locality buys. Predict the output of the two calls.
def cat_twice(part1, part2):
cat = part1 + part2
print(cat)
cat_twice('a', 'b')
cat_twice('c', 'd')| Call | What happens to cat | Output |
|---|---|---|
| first call | cat is created, holds 'ab', is destroyed | ab |
| second call | a NEW cat is created, holds 'cd' | cd |
| interference | none — the first cat was already gone | independent |
Run the first call and note what it leaves behind.
Why: It prints ab and then destroys its local names. Nothing survives.
Run the second call.
Why: It creates its own cat, unrelated to the first one, holding cd.
Ask what would happen if names were not local.
Why: The second call would overwrite the first's cat — harmless here, but fatal if the first call were still in progress, which is exactly what happens in recursion.
Figure (svg): Two state diagrams side by side showing cat holding ab during the first call and cd during the second, with nothing in between
ab then cd. Each call creates its own cat and destroys it, so the two calls cannot affect each other.
Verify: Check that no cat exists between the calls.
Why: Inserting print(cat) between the two calls gives a NameError, which shows the first call's variable really was destroyed rather than merely reassigned. That is what makes the second call independent rather than merely lucky.
Trap
A student writes a function that computes a total into a local variable, calls it, and then tries to use that variable in the main program.
Treat a function as a block of code that happens to be indented
Why: If it were merely a block, its variables would persist, and in some languages they would.
The NameError that follows is confusing precisely because the function obviously worked — the value was computed, and now it is unreachable.
A function's local names are private and temporary by design.
Decide what the caller needs to receive
Why: If the caller needs the total, the function must hand it back rather than leave it lying around.
Use a return value for that
Why: Which is chapter 6's subject, and the reason the fruitful/void distinction is at the end of this lesson.
Until then, a function that must communicate can print. That is a real limitation of every function you have written so far, and the book resolves it three chapters from now.
Discrimination
The test is where the name was created, not what kind of value it holds.
Sort into buckets
For each name in the program above, is it local to f?
Socratic
Python could keep a function's variables around. Argue about why it does not.
Discussion prompt
Name one thing that would go wrong if a function's local variables survived after it returned, and one thing that would become easier.
Hint: Think about a function called twice, and about a function that calls itself.
Answer:
What would go wrong: two calls would share variables. A function that calls itself — recursion, in chapter 5 — would overwrite its own values on every level, and could not work at all.
There is a memory cost too: every call would leave debris behind, and a program that made a million calls would keep a million sets of variables alive.
What would become easier: getting a result out. As things stand, a function's computed values are unreachable unless it prints or returns them, which is exactly the limitation chapter 6 addresses. Locality is a genuine trade — the price is that you must be deliberate about what comes back.
Counterexample
Locality says the caller cannot see the function's names. Test the other direction.
Discussion prompt
Locality means __main__ cannot see cat. Does it follow that cat_twice cannot see line1? Construct a small experiment that would settle it, and predict the outcome.
Hint: Write a function that uses a name it never created and never received.
Answer:
The experiment: define def show(): print(z) with no parameter, assign z = 5 in the main program, and call show().
It prints 5. So the relationship is not symmetric — a function CAN see names from the enclosing program, even though the program cannot see the function's names.
This is worth knowing and worth being wary of. It makes it possible to use a value by accident that you meant to pass as a parameter, and the program works, right up until the name changes elsewhere. Chapter 11 discusses this deliberately under the heading of global variables.
Section
Section 2
Concept
To keep track of which variables can be used where, it is sometimes useful to draw a stack diagram. Like state diagrams, stack diagrams show the value of each variable, but they also show the function each variable belongs to.
frame — A box with the name of a function beside it and the parameters and variables of that function inside it.
In the book's example, print_twice was called by cat_twice, and cat_twice was called by __main__. So part1 has the same value as line1, part2 the same value as line2, and bruce the same value as cat.
Think Python, 2nd edition — Allen B. Downey §3.8-3.12, pp. 23-23
Picture it
Read it downward: each frame was called by the one above it.
Figure (svg): A stack diagram with three frames, main at the top holding line1 and line2, cat_twice below it holding part1 part2 and cat, and print_twice at the bottom holding bruce
Note the repeated values. part1 has the same value as line1 because that value was passed; there are two names for it, in two frames, and neither knows about the other.
Worked example
Build it frame by frame, in the order the calls happen.
def print_twice(bruce):
print(bruce)
print(bruce)
def cat_twice(part1, part2):
cat = part1 + part2
print_twice(cat)
line1 = 'Bing tiddle '
line2 = 'tiddle bang.'
cat_twice(line1, line2)| Line | What happens | The stack |
|---|---|---|
| 9-10 | two assignments in __main__ | one frame: line1, line2 |
| 11 | call cat_twice | second frame: part1, part2 |
| 6 | cat is created | second frame gains cat |
| 7 | call print_twice | third frame: bruce |
Start with __main__.
Why: When you create a variable outside of any function, it belongs to __main__. So line1 and line2 go in the topmost frame.
Add a frame for each call, below the caller.
Why: cat_twice was called by __main__, so its frame goes beneath. Its parameters appear in it immediately, already referring to the values passed.
Add local variables as the body creates them.
Why: cat appears in cat_twice's frame when line 6 runs — not before.
Add the next frame when the next call happens.
Why: print_twice was called by cat_twice, so it goes below, with bruce referring to the same value cat does.
Figure (svg): The state of the program after each line of Worked example drawing the diagram for a two-level call, drawn as a ladder with one rung per traced line
Three frames, stacked: __main__ with two names, cat_twice with three, print_twice with one. The bottom frame is the function currently running.
Verify: Check that each parameter's value matches its argument's.
Why: part1 matches line1, part2 matches line2, and bruce matches cat. Each parameter refers to the same value as its corresponding argument — so if any pair disagreed, the diagram would be wrong, and this is the check that catches it.
Invariant
Step through the book's example and watch the stack change height.
Step through it
Between frames 4 and 5, how many names were destroyed — and which frame was removed first?
Four names went: bruce, then part1, part2 and cat. print_twice's frame was removed first, because the most recent call is always the first to finish. That ordering is the defining property of a stack.
Worked example
The stack grows on a call and shrinks on a return. Track both directions.
# during print_twice: __main__ / cat_twice / print_twice
# print_twice returns: __main__ / cat_twice
# cat_twice returns: __main__
# program ends: (empty)| Stack height | What just happened | Effect on names |
|---|---|---|
| 3 frames | print_twice is running | bruce exists |
| 2 frames | print_twice returned; its frame is gone | bruce destroyed |
| 1 frame | cat_twice returned; its frame is gone | part1, part2, cat destroyed |
| 0 frames | the program ends | line1, line2 destroyed |
Watch the frame disappear on return.
Why: When a function returns, its frame is removed, and every name in it goes with it. That is the mechanism behind local variables are destroyed.
Notice the order.
Why: Frames are removed in the reverse of the order they were added — the most recent call finishes first. That is what makes it a stack rather than a list.
Follow it to the end.
Why: Even __main__ goes away when the program ends, which is why nothing survives a program's exit.
Figure (svg): A flow chart showing frames being added on each call and removed on each return, with the stack height beside each step
The stack grows by one frame per call and shrinks by one per return, always removing the most recently added frame. Destroying a local variable and removing a frame are the same event.
Verify: Match this against the flow-of-execution picture from lesson 3b.
Why: The detour into a function is the frame being pushed; the return is the frame being popped. Two descriptions of one mechanism — and noticing that they are the same thing is what makes the stack diagram a picture of the flow rather than a separate idea.
Trap
Asked to draw the stack for a program where print_twice is called twice, a student draws one print_twice frame and updates it.
Think of a frame as belonging to the function
Why: The frame has the function's name beside it, so the association is natural.
It belongs to the CALL. Two calls to the same function produce two frames, at different times or even at the same time.
One frame per active call, not one per function.
Add a frame when a call begins, remove it when that call returns
Why: Two sequential calls produce two frames one after another, never both at once.
Expect several frames with the same name when a function calls itself
Why: This is exactly what recursion looks like on a stack diagram, and it is why each level can have its own values.
The book's phrasing is careful about this: the frames are arranged in a stack that indicates which function called which. It is about calls, and a call is an event rather than a piece of code.
Prediction
Count active calls, not function definitions.
def a():
b()
def b():
c()
def c():
print('deep')
a()| Call | Called by | Stack height |
|---|---|---|
| a() | called from __main__ | 2 frames |
| b() | called from a | 3 frames |
| c() | called from b | 4 frames |
Predict first
At the moment print('deep') runs, how many frames are on the stack?
Correct: Four — __main__, a, b and c, since none of them has returned yet.
Why: Every call adds a frame and none of these has returned: a is paused waiting for b, b is paused waiting for c, and __main__ is paused waiting for a. The easy mistake is to count three, forgetting __main__, but the book is explicit that it is a frame like any other — variables created outside any function belong to it.
Matching
Use the book's example. Every name lives in exactly one frame.
Match the pairs
Why: Notice that cat_twice's frame holds two kinds of name — parameters, created by the call, and locals, created by the body — and the diagram does not distinguish them, because their behaviour is identical. Both are local and both vanish on return. The distinction matters when you are writing the function and stops mattering once it is running.
Faded example
Fill in the frame name for each of the three variables.
Fill in the blanks
line1 belongs to __main__
cat belongs to cat_twice
bruce belongs to print_twice
Why: The rule is simply where the name was created. line1 was assigned outside any function, so it belongs to __main__ — the special name for the topmost frame. cat was assigned inside cat_twice's body. bruce is print_twice's parameter, created by the call. Being able to answer this for any name is exactly what the stack diagram is for.
Section
Section 3
Concept
If an error occurs during a function call, Python prints the name of the function, the name of the function that called it, and the name of the function that called that, all the way back to __main__. This list of functions is called a traceback.
traceback — A list of the functions that were executing when an error occurred, printed innermost last.
Traceback (innermost last):
File "test.py", line 13, in __main__
cat_twice(line1, line2)
File "test.py", line 5, in cat_twice
print_twice(cat)
File "test.py", line 9, in print_twice
print(cat)
NameError: name 'cat' is not defined| Entry | What it tells you | Position |
|---|---|---|
| line 13, __main__ | the outermost call | top of the traceback |
| line 5, cat_twice | called by __main__ | middle |
| line 9, print_twice | called by cat_twice, and where it failed | bottom |
| NameError | the error itself | last line |
The traceback tells you what program file the error occurred in, and what line, and what functions were executing at the time. It also shows the line of code that caused the error. The order of the functions in the traceback is the same as the order of the frames in the stack diagram, and the function that is currently running is at the bottom.
Think Python, 2nd edition — Allen B. Downey §3.8-3.12, pp. 23-24
Picture it
Two representations of one thing. Learn to see them as the same picture.
Figure (svg): Two columns showing the frames of a stack diagram beside the corresponding lines of a traceback in the same order
This is why the stack diagram is worth learning to draw: it is the mental model that makes tracebacks readable, and tracebacks are how you will meet nearly every error from now on.
Worked example
Four pieces of information are in here. Extract all four.
Traceback (innermost last):
File "test.py", line 13, in __main__
cat_twice(line1, line2)
File "test.py", line 5, in cat_twice
print_twice(cat)
File "test.py", line 9, in print_twice
print(cat)
NameError: name 'cat' is not defined| Question | Answer | Where it comes from |
|---|---|---|
| where it failed | line 9, inside print_twice | the bottom entry |
| what failed | print(cat) | the line shown under it |
| how it got there | __main__ called cat_twice called print_twice | read downward |
| what went wrong | NameError: cat is not defined | the last line |
Read the last line first.
Why: It names the error and explains it. NameError, and the name in question is cat. Everything above is about location, not about what went wrong.
Read the bottom entry next.
Why: The function that is currently running is at the bottom, so the failure was inside print_twice at line 9, on the statement print(cat).
Read upward for the route.
Why: print_twice was called from line 5 of cat_twice, which was called from line 13 of __main__. That is the chain of calls that led here.
Diagnose.
Why: cat is local to cat_twice, so it exists in cat_twice's frame and not in print_twice's. Accessing it from print_twice is a NameError even though the name genuinely exists one frame up.
Figure (svg): The state of the program after each line of Worked example reading the book's traceback, drawn as a ladder with one rung per traced line
The error is a NameError for cat, raised at line 9 inside print_twice, which was reached from cat_twice at line 5 and from __main__ at line 13. The cause is that cat belongs to a different frame.
Verify: Check the traceback against the stack diagram from the previous section.
Why: The three entries match the three frames, in the same order. That correspondence is what the book means by saying the order of the functions in the traceback is the same as the order of the frames — and it means a traceback lets you reconstruct the stack without drawing anything.
Prediction
One rule answers this, and it is stated in the traceback's own header.
Predict first
In a traceback with three entries, which one names the function that was actually running when the error occurred?
Correct: The last entry — the function that is currently running is at the bottom.
Why: The header says innermost last, which is exactly this claim: entries are printed outermost first, so the deepest call — the one that was executing — is at the bottom, immediately above the error message. This is also why the error line and the failing function end up adjacent, which is convenient once you know to read upward from there.
Worked example
The line that failed is not always the line that is wrong. Practise telling them apart.
File "prog.py", line 12, in __main__
show_total(items)
File "prog.py", line 4, in show_total
print(total / count)
ZeroDivisionError: division by zero| Question | Answer | Next step |
|---|---|---|
| where it failed | line 4, in show_total | the division |
| what value was wrong | count was zero | not shown, but implied |
| where count came from | computed earlier, or passed in | look upward |
Read the error and the failing line.
Why: A ZeroDivisionError on line 4, so count was zero at that moment.
Ask whether line 4 is wrong.
Why: Probably not. Dividing by count is a reasonable thing to write; the problem is that count held zero.
Follow the traceback upward to find where the value came from.
Why: The call on line 12 passed items. If count is derived from items, then an empty items is the real cause, and line 12 or earlier is where to look.
Figure (svg): A traceback panel with each entry labelled by what it tells the reader
The failure is at line 4 but the fault is upstream — an empty collection, or a count that was never incremented. The traceback tells you where the program broke, and the frames above tell you where to look for why.
Verify: Ask what you would print to confirm the diagnosis.
Why: Printing count just before line 4 confirms it is zero, and printing items at line 12 shows whether the emptiness came from the caller. Turning a traceback into two print statements is the standard move, and it works because the traceback has already told you which two places to put them.
Trap
A student sees a traceback, reads the first line under the header, and starts investigating __main__.
Read a block of text top to bottom
Why: It is what you do with everything else, and the first entry is the most prominent.
The first entry is the OUTERMOST call — usually the least informative one, and often a line that is entirely correct.
Read a traceback from the bottom, in two moves.
Last line first: what kind of error, and what does the message name?
Why: This tells you what sort of problem you are looking for before you look anywhere.
Then the bottom entry: which function and which line
Why: The function that is currently running is at the bottom, so that is where execution actually stopped.
Only then work upward, and only if the failing line looks correct — because then the wrong value came from somewhere above. The header even says so: innermost last.
Ranking
Outermost first. The call chain runs __main__ to load_data to parse_line.
Put in order
Why: Outermost call first, then each call it made, with the deepest last and the error message after all of them. The order is the same as the frames in a stack diagram read top to bottom, which is the correspondence worth internalising: if you can draw one you can read the other.
Notation
Each traceback entry has three pieces of information packed into two lines.
Annotate
That last point is why chapter 6 introduces a development method that checks values as it goes. A traceback locates a failure; it does not explain it.
Real world
This is a skill you will use for as long as you write Python.
Discussion prompt
You get a traceback fifteen entries deep, from code that includes libraries you did not write. How do you use it, and which entries do you actually care about?
Hint: Most of the fifteen are inside somebody else's code.
Answer:
Read the last line for the error kind, as always. Then scan upward for the LOWEST entry that names a file you wrote — that is the last point where your code was in control.
Everything below that is inside a library, and the failure there is usually a consequence of what your line handed it rather than a bug in the library.
So a fifteen-entry traceback typically has two entries worth reading: the deepest one in your own code, and the error message. Knowing that turns an intimidating wall of text into two lines, and it works because the traceback is just the stack, and the stack records who called whom.
Section
Section 4
Concept
Some functions return results; for lack of a better name, the book calls them fruitful functions. Others perform an action but do not return a value, and those are called void functions.
void function — A function that performs an action but does not return a value. Calling one produces None.
>>> result = print_twice('Bing')
Bing
Bing
>>> print(result)
None
>>> type(None)
<class 'NoneType'>| Expression | What it does | Result |
|---|---|---|
| print_twice('Bing') | a void function: it acts, it does not return | prints two lines |
| result | assigned whatever came back | None |
| type(None) | None has its own type | NoneType |
The value None is not the same as the string 'None'. It is a special value with its own type. All the functions written so far in this book are void; fruitful ones begin in chapter 6.
Think Python, 2nd edition — Allen B. Downey §3.8-3.12, pp. 24-24
Picture it
Both kinds of function do something. Only one of them hands you a value afterwards.
Figure (svg): Two columns contrasting a fruitful function that returns a value with a void function that returns None
None is not a failure or an error. It is Python's way of saying there was no result, and it is the honest answer for a function whose job was to do rather than to compute.
Worked example
This script computes something correct and useful, and throws it away. Find where.
import math
math.sqrt(5)| Line | What happens | Result |
|---|---|---|
| math.sqrt(5) | the square root is computed | 2.23606797749979 |
| nothing stores it | the value is discarded | gone |
| nothing displays it | in script mode a bare expression is silent | no output |
Confirm the computation happens.
Why: The call runs and produces a value. Nothing is wrong with the arithmetic.
Ask what happens to the value.
Why: In a script, if you call a fruitful function all by itself, the return value is lost forever.
State the fix.
Why: When you call a fruitful function you almost always want to do something with the result: assign it to a variable, or use it as part of an expression, or print it.
Figure (svg): The state of the program after each line of Worked example losing a return value, drawn as a ladder with one rung per traced line
The script computes the square root of 5 and immediately discards it, producing no output. It is not very useful — which is the book's own verdict on it.
Verify: Run the same line at the interactive prompt and watch the difference.
Why: At the prompt it displays 2.236..., because the interpreter shows the value of any expression you type. The value was being produced all along; script mode simply had nowhere to show it. That is lesson 2a's mode difference, applied to a return value.
Prediction
The function prints. That is not the same as returning.
def show(x):
print(x)
v = show(7)| Step | What happens | State |
|---|---|---|
| show(7) | the body runs and prints 7 | 7 appears |
| what comes back | nothing was returned | None |
| v | gets None | v -> None |
Predict first
After this runs, what does v refer to?
Correct: None. The function printed 7 as a side effect, but it returned nothing, so None is what the call produced.
Why: Two separate things happened: the body displayed 7, and the call produced a value for the assignment to use. Since show is a void function, that value is None. The 7 on the screen is not available to the program at all — it went to the display, not back to the caller, and nothing can retrieve it.
Worked example
It must return something — every call produces a value. Find out what.
>>> result = print_twice('Bing')
Bing
Bing
>>> print(result)
None
>>> result == 'None'
False| Step | What happens | Value |
|---|---|---|
| the call | prints two lines | the action happens |
| result | gets what came back | None |
| result == 'None' | None is not the string 'None' | False |
Notice that the action still happened.
Why: Two lines were printed. A void function is not a function that does nothing; it is a function whose point is its effect.
Look at what was assigned.
Why: If you assign the result of a void function to a variable, you get a special value called None.
Check that None is not text.
Why: The value None is not the same as the string 'None'. It has its own type, NoneType, and comparing them gives False.
Figure (svg): A state diagram showing result pointing at None, and a separate name pointing at the string None, with their types marked
None — a special value with its own type, returned by every function that does not return anything else. The printing still happened; there was simply no result to hand back.
Verify: Ask type() about both None and the string.
Why: type(None) reports NoneType and type('None') reports str. Two different types settles the question definitively, and it is a better check than comparing them, because it says why they differ rather than merely that they do.
Trap
A student writes total = print(a + b), expecting total to hold the sum because the sum appeared on the screen.
Confuse what a function DISPLAYS with what it RETURNS
Why: Both feel like output, and at the interactive prompt they even look similar.
total ends up holding None, and the next line that uses it fails with a TypeError about NoneType — an error whose message rarely points back at the real cause.
Displaying and returning are different things, done by different mechanisms.
Compute the value, THEN display it, as two steps
Why: total = a + b, and then print(total). The first produces the value; the second shows it.
Treat a TypeError mentioning NoneType as a signal
Why: It nearly always means a void function's result was assigned or used. Look for a print or an append on the right of an equals sign.
This is one of the most common bugs in early Python, and it is entirely explained by the fruitful/void distinction — which is why the book names both kinds rather than leaving void functions unnamed.
Discrimination
Ask whether the call produces a value worth keeping.
Sort into buckets
Sort each function by whether it is fruitful or void.
Two truths and a lie
Two are true. Keep the lie.
Eliminate the wrong options
Rule out the two true statements.
Survives elimination: C
Why: C is the lie, and the book warns about it explicitly: the value None is not the same as the string 'None'. They have different types, and comparing them gives False. The confusion is understandable because printing None displays the four characters None — but printing 42 displays two characters too, and nobody concludes that 42 is a string.
Edge cases
print is a void function. Test whether the rule applies to built-ins as well.
Discussion prompt
Predict what print(print('hi')) displays, and in what order. Then explain what it shows about built-in functions.
Hint: The inner call runs first, as always.
Answer:
It displays hi, and then None, on two lines.
The inner print runs first, displaying hi, and returns None. That None is then the argument to the outer print, which displays it.
So print is a void function exactly like print_twice, and the rule is not special to functions you write. This is also a neat demonstration of composition from lesson 3a — the inner call is fully evaluated, including its side effect, before the outer one begins.
Section
Section 5
Concept
It may not be clear why it is worth dividing a program into functions. The book gives four reasons, and they are worth knowing because they are also the criteria for deciding when to write one.
Notice that three of the four are about debugging or change rather than about the program working. Functions are not primarily a way of making a program run; they are a way of making it possible to keep working on one.
Think Python, 2nd edition — Allen B. Downey §3.8-3.12, pp. 24-25
Picture it
Both work. Only one of them can be changed safely.
Figure (svg): Two columns comparing repeated code with a single function called three times, showing what happens when a change is needed
The right-hand column is also easier to read, because the name says what the four lines are for — which is the first reason, obtained for free.
Worked example
You have three lines that appear twice. Decide whether to make them a function.
print('-' * 40)
print('RESULTS')
print('-' * 40)
def print_header():
print('-' * 40)
print('RESULTS')
print('-' * 40)| Reason | Does it apply here? | Verdict |
|---|---|---|
| reason 1: naming | print_header says what the three lines are | yes |
| reason 2: repetition | the block appears twice | yes |
| reason 3: debug separately | marginal for three print statements | weak |
| reason 4: reuse | a header is useful in other programs | yes |
Check each reason in turn.
Why: This is a decision procedure rather than a matter of taste — the four reasons are a checklist.
Weigh them.
Why: Three of the four apply clearly. That is comfortably enough.
Notice which reason is weak.
Why: Debugging the parts separately hardly applies to three print statements, and pretending otherwise would be dishonest. A function can be worth writing on some of the reasons rather than all of them.
Figure (svg): The state of the program after each line of Worked example applying the four reasons to a decision, drawn as a ladder with one rung per traced line
Yes, write the function. Naming, de-duplication and reuse all apply; only the debug-separately reason is weak, and three out of four is a clear case.
Verify: Imagine the header changing from RESULTS to SUMMARY and count the edits.
Why: With the function, one edit. Without it, two — and if the block had appeared five times, five, with four opportunities to miss one. Counting edits is the concrete form of the second reason, and it is the argument that persuades people who find the others abstract.
Sorting
Four reasons, four situations. Match the strongest justification in each case.
Sort into buckets
For each situation, which of the four reasons is the strongest argument for writing a function?
Worked example
The book's account of debugging is unusually specific. Apply it to a real situation.
# symptom: print_header prints only two lines, not three
# hypothesis 1: one print statement is missing
# prediction: the body will have two prints, not three
# test: read the body
# result: three prints are present -- hypothesis rejected
# hypothesis 2: one line prints something invisible
# prediction: the middle print has an empty string
# test: read it again
# result: confirmed| Step | What it is | Requirement |
|---|---|---|
| hypothesis | an idea about what is going wrong | must be testable |
| prediction | what you would see if it were true | must be checkable |
| test | the check itself | cheap and specific |
| result | confirms or rejects | then repeat |
Form a hypothesis.
Why: Debugging is like an experimental science: once you have an idea about what is going wrong, you modify your program and try again.
Make a prediction from it.
Why: If your hypothesis was correct, you can predict the result of the modification. A hypothesis that predicts nothing cannot be tested and is not worth having.
Test, and take the answer seriously.
Why: If your hypothesis was wrong, you have to come up with a new one — not test the same one harder.
Figure (svg): A flow chart showing the debugging cycle of forming a hypothesis, predicting, testing, and either fixing or forming a new hypothesis
Two hypotheses, two predictions, two tests. The first was rejected and cost nothing; the second was confirmed and located the bug. This is the method, and its value is that it terminates.
Verify: Compare it with changing things at random until the symptom goes away.
Why: Random changes can make a symptom disappear without fixing the cause, and they leave you unable to say why the program now works. The hypothesis method ends with an explanation, which is the difference between a program you have fixed and one that has stopped complaining.
Trap
A student adopts the rule make it a function when you have written it twice and nothing else.
Use de-duplication as the only criterion
Why: It is the most concrete of the four reasons and the easiest to apply.
This misses the other three. A hundred-line program with no repetition at all still benefits enormously from being divided, because it can then be read, debugged and tested in pieces.
Any one of the four reasons is enough on its own.
Ask whether a group of statements deserves a name
Why: If you can name it, the name will make the rest of the program readable, and that is reason one by itself.
Ask whether you could test this piece alone
Why: If yes, dividing it out lets you debug the parts one at a time — reason three, which matters most in long programs.
Chapter 4 is a whole case study in exactly this: the code does not repeat much, and it is divided into functions anyway, for the other three reasons.
Prediction
This is the second reason, quantified.
Predict first
A block of code appears seven times. A requirement changes. Without a function, how many places must be edited, and what is the risk?
Correct: Seven, with six chances to miss one — and a missed copy produces an inconsistency rather than an error.
Why: That last part is what makes it dangerous. A missed edit does not produce a syntax error or a crash; it produces a program that behaves one way in six places and another way in the seventh, which is a semantic error from lesson 2b and therefore the hardest kind to find. With a function, the change is made once and cannot be made inconsistently.
Analogy
The book offers two analogies for debugging. Match each element to its counterpart.
Match the pairs
Why: Both analogies say the same thing: you cannot observe the cause directly, so you infer it from evidence and test the inference. The Holmes quotation the book uses — when you have eliminated the impossible, whatever remains, however improbable, must be the truth — is specifically about the value of ruling things OUT, which is why a rejected hypothesis is progress rather than a wasted step.
Explain it
The question a beginner actually asks, and it deserves a real answer.
Discussion prompt
A classmate says my program works fine as one long list of statements, so why should I split it into functions? Give them the two reasons most likely to persuade someone whose program currently works — and be honest about what functions cost.
Hint: Their program works today. What happens next week?
Answer:
The two that land: you only have to change things in one place, and you can test the parts separately when something goes wrong. Both are about the future rather than about today.
Be honest about the cost: functions add indirection. Reading a program with twenty functions means jumping around, and a badly named function is worse than no function at all.
The framing that usually persuades: a program is not finished when it works. It is finished when it is still working after the next three changes — and functions are how you keep that possible. Linux began as a program that switched between printing AAAA and BBBB, and it got where it is by being changeable.
Comparison
Fill the blanks. Each picture answers a different question about the same program.
Comparison matrix
| Picture | What it shows | When to draw it |
|---|---|---|
| state diagram | names and the values they refer to | when you lose track of what a variable holds |
| stack diagram | which function each name belongs to, and who called whom | when a name seems to exist in one place and not another |
| traceback | the stack at the moment an error occurred | whenever Python hands you one |
The third is the first two, printed by Python at the worst possible moment. Learning to draw the second is how you learn to read the third.
Pattern
Six steps, and the first three are pure reading.
Step 6 is the honest part. A test that confirms your hypothesis is progress and so is one that refutes it, and treating a refutation as a failure is what turns debugging into guessing.
Python documentation — More Control Flow Tools More Control Flow Tools
Check
The function has returned. Decide what is left.
def f():
temp = 99
f()
print(temp)| Line | What happens | Result |
|---|---|---|
| f() | temp is created inside f | temp exists during the call |
| the return | temp is destroyed | gone |
| print(temp) | the name does not exist here | NameError |
Check your understanding
What happens when this script runs?
Answer: C
Why: temp was created inside f, so it is local: it exists only while f is running and is destroyed when f terminates. By the time print(temp) runs, there is no such name in __main__, and Python reports a NameError exactly as it would for a name that had never existed.
Check
The header says innermost last. Use it.
File "p.py", line 20, in __main__
run()
File "p.py", line 8, in run
total = compute(data)
File "p.py", line 3, in compute
return 1 / n
ZeroDivisionError: division by zero| Entry | Function and line | Meaning |
|---|---|---|
| bottom entry | compute, line 3 | where it failed |
| middle entry | run, line 8 | who called compute |
| top entry | __main__, line 20 | who called run |
Check your understanding
Which function was executing when the error occurred?
Answer: C
Why: The function that is currently running is at the bottom of the traceback, immediately above the error message. Here that is compute, at line 3, on the statement that divides by n. The two entries above it record how execution got there: __main__ called run, and run called compute.
Check
Ask what the call hands back, not what appears on the screen.
def announce(x):
print('value:', x)
r = announce(5)| Step | What happens | Result |
|---|---|---|
| announce(5) | the body prints | value: 5 |
| what is returned | nothing | None |
| r | gets None | r -> None |
Check your understanding
What is the value of r?
Answer: C
Why: announce is a void function: it performs an action — printing — and returns no value. If you assign the result of a void function to a variable, you get None. The text that appeared on the screen went to the display, not back to the caller, and there is no way to recover it from within the program.
Real world
Local scope and the call stack are ideas about organisation, not about Python.
Discussion prompt
Think of an organisation where somebody delegates a task, which is delegated again. What corresponds to a frame, to a local variable, and to a traceback when something goes wrong at the deepest level?
Hint: Consider what each person knows, and what they report back.
Answer:
Each person handling the task is a frame. Their working notes are local variables — theirs, private, and thrown away when they hand back their result.
The traceback is what you get when you ask how did this land on your desk? and the answer is a chain: I was asked by her, who was asked by him. Reading it from the bottom finds who was actually doing the work.
And the reason locality is a good idea is the same in both cases: if everyone's working notes were shared, two people doing the same kind of task at the same time would overwrite each other. Private notes are what let the same procedure run twice at once.
Commit first
Answer, then rate your confidence. This combines locality with the fruitful/void distinction.
Predict first
A function assigns a result to a local variable and prints it, but has no return statement. The caller writes x = f(). What does x hold?
Correct: None. The function computed and printed a value, but printing is not returning, and a function with no return statement is void.
Why: Two facts combine here and both are from this lesson. The local variable holding the result was destroyed when the function returned, so it is unreachable. And because there was no return statement, the call produced None, which is what x now holds. The printed value went to the screen and is gone. This exact situation is the most common reason a beginner's program mysteriously ends up with None in a variable, and the fix — a return statement — is the subject of chapter 6.
Explain it
The stack diagram is the thing worth being able to draw for somebody.
Discussion prompt
A classmate cannot see why they get a NameError for a variable that they can clearly see in their own code, four lines above. Draw them the picture that explains it, and say what the fix would be.
Hint: The variable is visible on the page. That is not the same as being visible to the program.
Answer:
Draw two boxes, one labelled with the function's name and one labelled __main__, and put the variable inside the function's box. Then point at the line that failed and ask which box it is in.
The insight is that being four lines above on the page is irrelevant. What matters is which frame the name was created in, and a name in one frame is invisible from another.
The fix is either to pass the value in as an argument, or — from chapter 6 — to return it. Both are ways of moving a VALUE between frames, which is the only thing that can move between them.
Exit ticket
One honest answer. It decides what the next lesson opens with.
Predict first
Which of these is still least solid for you?
Correct: Whichever you picked is the right answer — this one is for you, not for a mark.
Why: These four have very different futures. Locality becomes intuitive within a few programs and then quietly underpins everything. The stack diagram is worth over-learning now, because chapter 5's recursion is genuinely hard without it and genuinely easy with it. Traceback reading improves every week for years and is the highest-value debugging skill in the book. And the fruitful/void distinction is about to stop being a curiosity: chapter 6 is entirely about writing functions that return things, and None is the value you will see whenever you forget to.
Connect it up
One page, from memory. Drawing this is the exercise.
Draw it
Draw the stack diagram for a program in which __main__ calls a function, which calls another function, at the moment the innermost one is running. Label every frame and every variable. Then, beside it, write out the traceback Python would print if the innermost function raised an error — and draw arrows connecting each traceback entry to the frame it corresponds to. Finally, mark on the diagram the exact moment at which each variable is destroyed.
Recap
Three pages, and chapter 3 is finished: you can write functions, and you can see what happens when one runs.
| If you remember one thing | It is this |
|---|---|
| From locality | A frame's names die with the frame. Only values can cross between frames. |
| From the stack diagram | One frame per CALL, not per function. __main__ is a frame too. |
| From tracebacks | Read the last line, then the bottom entry, then work upward. |
| From void functions | Printing is not returning. A function with no return statement gives you None. |
| From the four reasons | Three of the four are about changing the program later, not about running it. |
Chapter 4 is a case study: a set of drawings made with the turtle module, developed through encapsulation, generalization, interface design and refactoring — the first time in this book that the process of writing a program is the subject rather than the syntax.
Think Python, 2nd edition — Allen B. Downey §3.8-3.12, pp. 22-25 — everything on these slides traces back here
Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.