3c Local Variables, Stack Diagrams, and Why Functions

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

What this lesson covers

The lesson, slide by slide

1. Lesson 3c Local Variables, Stack Diagrams, and Why Functions

Title

Python · Chapter 3 — Functions

§3.8-3.12, pp. 22-25

2. By the end of this lesson you can

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

3. Before we start: where do a function's names live?

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.

4. The one idea behind this lesson: each call gets its own set of names

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

The book's figure 3.1. Three frames, each with its own names.

Think Python, 2nd edition — Allen B. Downey §3.8-3.12, pp. 22-23 — figure 3.1, the stack diagram

5. Local variables: created by a call, destroyed by its return

Section

Section 1

6. What *local* means

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)
NameWhy it is localLifetime
part1, part2parameters — localcreated by the call
catcreated inside the body — localcreated by line 2
after the callall three destroyednone 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

7. Picture it: names that exist only during the call

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

Same kind of name, different lifetime.

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.

8. Worked example: a local variable used outside its function

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
MomentDoes cat exist?Result
during the callcat exists inside cat_twice'Bing tiddle tiddle bang.'
at the returncat is destroyedgone
print(cat)the name does not exist hereNameError

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

The whole run at once: each drop is one line of the program.

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.

9. Predict: which names exist here?

Prediction

The function has finished. Decide what survives.

def f(x):
    y = x * 2
    print(y)

z = 5
f(z)
LineWhat it createsAfter the call
z = 5created in __main__survives
f(z)creates x and y inside fboth local
after f returnsx and y destroyedonly z remains

Predict first

After the last line runs, which names still exist?

  • x, y and z
  • only z
  • only x and y
  • none of them

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.

10. Worked example: two calls do not interfere

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')
CallWhat happens to catOutput
first callcat is created, holds 'ab', is destroyedab
second calla NEW cat is created, holds 'cd'cd
interferencenone — the first cat was already goneindependent

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

Not one variable changing. Two variables, each with its own lifetime.

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.

11. Trap: expecting a function to leave its results behind

Trap

The 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.

The fix

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.

12. Discriminate: local or not?

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?

local to f
x, the parameter of f; y, assigned inside f
not local to f
z, assigned in the main program; f, the function's own name; print, used inside f; the value 5
loc
Both were created by the call: one as a parameter named in the header, one by an assignment in the body. Both are destroyed when f returns.
not
z belongs to __main__. f is a name in __main__ too — the def statement created it there. print is a built-in name available everywhere. And 5 is a value, not a name at all, so the question does not apply to it.

13. Think it through: why destroy them?

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.

14. Find the counterexample: can a function see the caller's names?

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.

15. The stack diagram: one frame per active call

Section

Section 2

16. Drawing which variables can be used where

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

17. Picture it: figure 3.1, frame by frame

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

Three frames. The bottom one is where the program is right now.

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.

18. Worked example: drawing the diagram for a two-level call

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)
LineWhat happensThe stack
9-10two assignments in __main__one frame: line1, line2
11call cat_twicesecond frame: part1, part2
6cat is createdsecond frame gains cat
7call print_twicethird 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

The whole run at once: each drop is one line of the program.

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.

19. Watch the stack: frames appearing and disappearing

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?

  1. Two assignments have run. One frame, two names, both belonging to __main__.
  2. cat_twice was called. A new frame appears with its two parameters, already referring to the passed values.
  3. Line 2 of the body ran, creating cat. The frame gains a third name without any new frame appearing.
  4. print_twice was called from inside cat_twice. Three frames now, and bruce refers to the same value as cat.
  5. Both functions have returned. Their frames are gone and every name in them was destroyed; only __main__ remains.

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.

20. Worked example: what happens to the stack on return

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 heightWhat just happenedEffect on names
3 framesprint_twice is runningbruce exists
2 framesprint_twice returned; its frame is gonebruce destroyed
1 framecat_twice returned; its frame is gonepart1, part2, cat destroyed
0 framesthe program endsline1, 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.

21. Trap: drawing one frame per function rather than per call

Trap

The 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.

The fix

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.

22. Predict: how many frames?

Prediction

Count active calls, not function definitions.

def a():
    b()

def b():
    c()

def c():
    print('deep')

a()
CallCalled byStack height
a()called from __main__2 frames
b()called from a3 frames
c()called from b4 frames

Predict first

At the moment print('deep') runs, how many frames are on the stack?

  • One
  • Three
  • Four
  • Two

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.

23. Match each name to the frame it belongs to

Matching

Use the book's example. Every name lives in exactly one frame.

Match the pairs

  • a. line1
  • b. part1
  • c. cat
  • d. bruce
  • r1. __main__
  • r2. cat_twice, as a parameter
  • r3. cat_twice, as a local variable
  • r4. print_twice, as a parameter

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.

24. Complete it: which frame does this name belong to?

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.

25. The traceback: a stack diagram Python prints for you

Section

Section 3

26. Reading an error that happened deep inside a call

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
EntryWhat it tells youPosition
line 13, __main__the outermost calltop of the traceback
line 5, cat_twicecalled by __main__middle
line 9, print_twicecalled by cat_twice, and where it failedbottom
NameErrorthe error itselflast 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

27. Picture it: the traceback IS the stack diagram

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

Same order, same information. One you draw; one Python prints.

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.

28. Worked example: reading the book's traceback

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
QuestionAnswerWhere it comes from
where it failedline 9, inside print_twicethe bottom entry
what failedprint(cat)the line shown under it
how it got there__main__ called cat_twice called print_twiceread downward
what went wrongNameError: cat is not definedthe 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 whole run at once: each drop is one line of the program.

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.

29. Predict: which function was running?

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?

  • The first entry
  • The last entry
  • The middle entry
  • It depends on the error type

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.

30. Worked example: locating the real problem

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
QuestionAnswerNext step
where it failedline 4, in show_totalthe division
what value was wrongcount was zeronot shown, but implied
where count came fromcomputed earlier, or passed inlook 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.

31. Trap: reading the traceback from the top

Trap

The 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.

The fix

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.

32. Rank: put these traceback entries in the order Python prints them

Ranking

Outermost first. The call chain runs __main__ to load_data to parse_line.

Put in order

  1. line 20, in __main__
  2. line 11, in load_data
  3. line 3, in parse_line
  4. ValueError: invalid literal for int()

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.

33. Decode the notation: what each part of an entry means

Notation

Each traceback entry has three pieces of information packed into two lines.

Annotate

  • The file name says which source file. In a one-file program it is always the same, and at the interactive prompt it appears as stdin instead.
  • The line number says exactly which line of that file. This is the single most useful number in the whole traceback.
  • The name after in is the function that line belongs to — or __main__ for a line outside any function.
  • The indented line below is the source of that line, printed so you do not have to open the file to see it.
  • Together they answer: which file, which line, which function, and what does that line say. Four questions, two lines.
  • Note what is NOT here: the values of any variables. The traceback tells you where, never what — which is why diagnosing usually needs a print statement as well.

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.

34. Where tracebacks pay off

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.

35. Fruitful functions, void functions, and None

Section

Section 4

36. Functions that hand something back, and functions that do not

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'>
ExpressionWhat it doesResult
print_twice('Bing')a void function: it acts, it does not returnprints two lines
resultassigned whatever came backNone
type(None)None has its own typeNoneType

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

37. Picture it: what comes back

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

Both were called the same way. Only one produced something worth keeping.

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.

38. Worked example: losing a return value

Worked example

This script computes something correct and useful, and throws it away. Find where.

import math
math.sqrt(5)
LineWhat happensResult
math.sqrt(5)the square root is computed2.23606797749979
nothing stores itthe value is discardedgone
nothing displays itin script mode a bare expression is silentno 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 whole run at once: each drop is one line of the program.

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.

39. Predict: what does this assign?

Prediction

The function prints. That is not the same as returning.

def show(x):
    print(x)

v = show(7)
StepWhat happensState
show(7)the body runs and prints 77 appears
what comes backnothing was returnedNone
vgets Nonev -> None

Predict first

After this runs, what does v refer to?

  • 7
  • None
  • The string '7'
  • Nothing — v is not created

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.

40. Worked example: what a void function returns

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
StepWhat happensValue
the callprints two linesthe action happens
resultgets what came backNone
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

Two different values, two different types, similar spelling.

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.

41. Trap: assigning the result of a void function

Trap

The 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.

The fix

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.

42. Discriminate: fruitful or void?

Discrimination

Ask whether the call produces a value worth keeping.

Sort into buckets

Sort each function by whether it is fruitful or void.

fruitful — returns a result
math.sqrt; int; type; len
void — performs an action
print; print_twice, from this chapter
fruit
Each of these hands something back that you would want to keep: a number, a converted value, a type, a length. Calling one and discarding the result is nearly always a mistake.
void
Each of these exists for its effect. print puts characters on the screen and print_twice does it twice; neither has a result, so both give None if you assign them.

43. Two truths and a lie: None

Two truths and a lie

Two are true. Keep the lie.

Eliminate the wrong options

Rule out the two true statements.

  • A. None has its own type, called NoneType
  • B. A void function's call produces None
  • C. None is the same as the string 'None', just written without quotes

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.

44. Push the boundary: does print return None too?

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.

45. Why functions? The four reasons, and what debugging really is

Section

Section 5

46. Why it is worth the trouble

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

47. Picture it: the same program, with and without functions

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 second reason, made concrete: if you make a change, you only have to make it in one place.

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.

48. Worked example: applying the four reasons to a decision

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)
ReasonDoes it apply here?Verdict
reason 1: namingprint_header says what the three lines areyes
reason 2: repetitionthe block appears twiceyes
reason 3: debug separatelymarginal for three print statementsweak
reason 4: reusea header is useful in other programsyes

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

The whole run at once: each drop is one line of the program.

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.

49. Sort: which reason justifies each function?

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?

naming a group of statements
Twelve lines that together do one describable thing
eliminating repetition
The same six lines appear four times in the program
debugging the parts one at a time
A 200-line program that works, except somewhere in the middle
reuse across programs
A date-formatting routine you will want in your next program too
name
Twelve lines doing one describable thing is exactly the case for naming: the name replaces twelve lines of reading with three words, and it documents intent that the code cannot state.
dedupe
Four copies means four edits for every change and four chances to miss one. Making it a function reduces that to one edit.
debug
A long program that fails somewhere unknown is the classic case for division. Split into functions, you can test each part and narrow the search to one of them.
reuse
Something useful beyond this program is worth isolating, because once you write and debug it you can use it again without rewriting or re-trusting it.

50. Worked example: debugging as an experimental science

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
StepWhat it isRequirement
hypothesisan idea about what is going wrongmust be testable
predictionwhat you would see if it were truemust be checkable
testthe check itselfcheap and specific
resultconfirms or rejectsthen 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.

51. Trap: writing functions only when code repeats

Trap

The 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.

The fix

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.

52. Predict: how many edits?

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?

  • One, since the change is the same everywhere
  • Seven, with six chances to miss one and create an inconsistency
  • Seven, but a mistake would be caught by a syntax error
  • It depends on the length of the block

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.

53. Analogical pivot: debugging as detective work

Analogy

The book offers two analogies for debugging. Match each element to its counterpart.

Match the pairs

  • a. the symptom you observe
  • b. your idea about the cause
  • c. changing the program and re-running
  • d. eliminating a possibility
  • r1. the clues at the scene
  • r2. the hypothesis
  • r3. the experiment
  • r4. when you have eliminated the impossible...

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.

54. Explain it: why bother with functions?

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.

55. Compare: the three pictures of this chapter

Comparison

Fill the blanks. Each picture answers a different question about the same program.

Comparison matrix

PictureWhat it showsWhen to draw it
state diagramnames and the values they refer towhen you lose track of what a variable holds
stack diagramwhich function each name belongs to, and who called whomwhen a name seems to exist in one place and not another
tracebackthe stack at the moment an error occurredwhenever 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.

56. The procedure: diagnosing an error inside a function call

Pattern

Six steps, and the first three are pure reading.

  1. Read the last line of the traceback: the error's name and its message.
  2. Read the bottom entry: which function was running, and which line of it.
  3. Look at the failing line and ask whether it is wrong, or whether it was given something wrong.
  4. If it was given something wrong, follow the traceback upward to find where that value came from.
  5. Add a print statement in the frame where you suspect the value went bad, and run again.
  6. Compare what printed with what you expected — and if they agree, your hypothesis was wrong, so form another.

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

57. Check yourself 1 of 3: locality

Check

The function has returned. Decide what is left.

def f():
    temp = 99

f()
print(temp)
LineWhat happensResult
f()temp is created inside ftemp exists during the call
the returntemp is destroyedgone
print(temp)the name does not exist hereNameError

Check your understanding

What happens when this script runs?

  • A. It prints 99
  • B. It prints None
  • C. A NameError, because temp is local to f (correct)
  • D. Nothing at all

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.

Why A tempts people
The value 99 did exist, but only inside f's frame. When that frame was removed the name went with it, and nothing in __main__ ever referred to the value.
Why B tempts people
None is what a void function RETURNS. It has nothing to do with local variables, and temp is not the return value of anything.
Why D tempts people
Something certainly happens: Python raises an error and prints a traceback. A silent exit would require the print statement to succeed and display nothing.

58. Check yourself 2 of 3: reading a traceback

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
EntryFunction and lineMeaning
bottom entrycompute, line 3where it failed
middle entryrun, line 8who called compute
top entry__main__, line 20who called run

Check your understanding

Which function was executing when the error occurred?

  • A. __main__
  • B. run
  • C. compute (correct)
  • D. It cannot be determined from a traceback

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.

Why A tempts people
__main__ is at the TOP, which means it is the outermost frame — the one that started the chain. It was paused waiting for run to finish, not executing.
Why B tempts people
run is in the middle, so it too was paused, waiting for compute. Its line number tells you where it will resume if compute ever returns.
Why D tempts people
A traceback is precisely a record of which functions were executing. Determining this is what it is for.

59. Check yourself 3 of 3: fruitful or void

Check

Ask what the call hands back, not what appears on the screen.

def announce(x):
    print('value:', x)

r = announce(5)
StepWhat happensResult
announce(5)the body printsvalue: 5
what is returnednothingNone
rgets Noner -> None

Check your understanding

What is the value of r?

  • A. 5
  • B. 'value: 5'
  • C. None (correct)
  • D. An error, because announce returns nothing

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.

Why A tempts people
5 was passed IN as an argument. Nothing sends it back out; the function would need a return statement to do that, and it has none.
Why B tempts people
This is what was displayed, not what was returned. print composes and shows that text but hands nothing back to announce, which in turn hands nothing back to the caller.
Why D tempts people
Returning nothing is not an error. Every call produces a value, and for a void function that value is None — which is exactly why None exists.

60. Where this shows up outside this course

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.

61. Confidence wager: commit before you check

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?

  • The value the function computed and printed
  • None
  • Nothing — x is not created
  • A NameError, because the local variable is not visible

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.

62. Explain it to someone else

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.

63. Exit ticket

Exit ticket

One honest answer. It decides what the next lesson opens with.

Predict first

Which of these is still least solid for you?

  • Locality: which names exist where, and when they are destroyed
  • Drawing a stack diagram with the right frames in the right order
  • Reading a traceback from the bottom up
  • Fruitful versus void, and where None comes from

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.

64. Synthesis: draw the map of this lesson

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.

65. What you can do now

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 thingIt is this
From localityA frame's names die with the frame. Only values can cross between frames.
From the stack diagramOne frame per CALL, not per function. __main__ is a frame too.
From tracebacksRead the last line, then the bottom entry, then work upward.
From void functionsPrinting is not returning. A function with no return statement gives you None.
From the four reasonsThree 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

Sources

  1. Think Python, 2nd edition — Allen B. Downey — Allen B. Downey, Think Python: How to Think Like a Computer Scientist, 2nd edition (Green Tea Press, 2015), §3.8-3.12, pp. 22-25
  2. Python documentation — More Control Flow Tools
  3. Python documentation — Errors and Exceptions

Want this taught 1-on-1? Alexander tutors Python — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108