Stack Frames and Stack Traces
A stack frame represents a single function call. You can visualize functions that call one another as virtual frames stacking on top of one another. The stack data structure is actually used for this!
When one function call returns its data to its caller, then its stack frame is removed from the stack. New stack frames are added and removed (along with their associated data) until the bottommost stack frame, sometimes called the module frame, gets the data it needs.
00:00
Before we can understand the last few commands, we need to first understand stack traces and frames. Take a look at this program. We’ve got three functions: add()
, multiply()
, and perform_multiplication()
.
00:16
At the bottom of the file, we call the perform_multiplication()
function with 2
and 3
. That function calls the multiply()
function, which will just multiply the two numbers the long way—by repeatedly calling the add()
function. I know this all seems pretty silly, but just stick with me. Basically, we have a chain of functions that calls one another.
00:43 A frame simply represents a function call. These frames are stacked on top of one another, and when a function returns, the stack frame representing it disappears. To make this more clear, let’s take a look at this diagram.
01:01 Now, this is oversimplified but it’ll explain what you need to know.
01:07
At the bottom of the stack, we have the <module>()
frame, which will represent all the function calls that are not nested within another function. Inside it, we have the result
variable, which is waiting to be initialized with some value. To get its value, it’s going to call the perform_multiplication()
function, and so we’ll add that to the stack.
01:33 That function wants to return something, but it’s not quite sure what yet.
01:39
To get that information, it needs to call multiply()
, and so we add that to the stack. That function will eventually return something, but it needs to repeatedly call add()
so it knows what to return.
01:54
This add()
function here is inside a for
loop, and so we’re going to end up calling the add()
function multiple times. On the first iteration, a
is 2
and product
is 0
, and so we call add()
with 0
and 2
.
02:12
The add()
function has been called, aka we’ve created a stack frame for it. And it’s got everything it needs to return now, so it adds those two numbers and returns 2
to the calling frame. That is assigned to the product
variable, and then we enter the second iteration.
02:33
Another add()
frame is created, this time with a product
of 2
and a value
of 2
.
02:39
It returns 4
to the calling frame. Finally, the multiply()
function calls add()
one last time, now with a product
of 4
and the same a
value of 2
.
02:53
As you can probably guess, the add()
function will return 6
. Once execution leaves the for
loop, the multiply()
frame realizes it has everything it needs to return to the caller, and so it gives the number 6
to the perform_multiplication()
frame, which then returns 6
to the <module>()
frame. 6
is then assigned to the result
variable, and that gets printed to the console. As you can see, frames represent individual function calls, which stack on top of one another. When one function returns, it disappears from the stack and its value is returned to the calling frame.
03:36
This happens repeatedly until the data gets all the way back to the <module>()
frame. As for a stack trace, well, you’ve probably seen that before. When your Python program runs into an unhandled exception, it prints a stack trace known as a traceback.
03:56
This literally traces back the exception from the frame in which it was thrown all the way back to the <module>()
frame, where it is unhandled.
04:06 This is helpful for debugging, because it tells us what function call—or stack frame—threw the exception, and what other frames it passed through before Python realized we are never going to handle it.
Become a Member to join the conversation.