Python Function Call Order and the Call Stack
A Python function can call a helper defined lower in the file, if the helper exists when the call runs. The call stack tracks unfinished calls.
Read Decomposition first. This lesson shows when a helper call works, and how to read a chain of calls when one fails.
Define a helper below its caller
def only creates the function. The body runs later, when the function is called.
def triple_then_add_one(n):
return triple(n) + 1
def triple(n):
return n * 3
print(triple_then_add_one(5))
This prints 16. Both def lines have run before the last line calls triple_then_add_one, so triple exists when it is needed.
Call a function before it is defined
What matters is the order the calls run in, not the order of the def lines. A call above the helper's def fails.
def double(n):
return half_of(n) * 4
try:
print(double(10))
except NameError as error:
print("NameError:", error)
def half_of(n):
return n // 2
This prints NameError: name 'half_of' is not defined. The call ran before Python reached def half_of.
The distinction that confuses people is defining versus calling. Inside a function body, a helper's name is only looked up when that line runs. At the top level, a call runs straight away.
So choose this layout: every def first, in any order, then the code that calls them at the bottom of the file. Order the def lines for reading, such as the main function first and its helpers below it.
Follow the call stack
When report calls average, and average calls total, all three calls are unfinished at once. Python keeps them on the call stack and returns to each one in reverse order.
def total(rolls):
print(" total starts")
return sum(rolls)
def average(rolls):
print(" average starts")
result = total(rolls) / len(rolls)
print(" average ends")
return result
def report(rolls):
print("report starts")
print("report got", average(rolls))
report([2, 4, 6])
The last call to start (total) is the first to finish. Then average resumes, then report.
Read the stack in a traceback
When an error happens deep in a chain of calls, the traceback lists the stack, outermost call first. This block catches the error. traceback.extract_tb() turns the error's __traceback__ into a list of stack entries, and each entry's .name is the function it was in.
import traceback
def total(rolls):
return sum(rolls)
def average(rolls):
return total(rolls) / len(rolls)
try:
average([])
except ZeroDivisionError as error:
for frame in traceback.extract_tb(error.__traceback__):
print(frame.name)
This prints <module> then average: the error happened in average, called from the top level. total([]) had already returned 0, so it is no longer on the stack. In a real traceback, read the last entry to find where the error happened, and the ones above it to see how the program got there.
Note: Each call has its own local variables, so two rolls in different functions do not clash. See Variable scope.
See also: Debugging, Create your own module.
Your turn
Complete average(rolls) so it calls total(rolls), which is defined below it. Then complete report(rolls) so it calls average and prints Average: followed by the result. Keep the call at the bottom.
def average(rolls):
# return total(rolls) divided by the number of rolls
pass
def report(rolls):
# print "Average:" and the result of average(rolls)
pass
def total(rolls):
return sum(rolls)
report([3, 5, 4])
report([6, 6])
Expected output
Average: 4.0
Average: 6.0