Frame Objects — Core Concepts
What Is a Frame Object?
When Python calls a function, it creates a frame object to hold all the runtime state for that particular invocation. While a code object describes what a function does (the instructions and constants), the frame object tracks where execution currently is and what values the variables hold right now.
Think of the code object as a recipe card and the frame as the kitchen counter where you have ingredients measured out, a timer running, and a bookmark on step 3. Multiple cooks can follow the same recipe simultaneously — each gets their own counter.
The Call Stack
Frames link together to form the call stack. When function a() calls b(), and b() calls c(), there are three frames. Each frame’s f_back attribute points to the frame that called it:
c's frame → f_back → b's frame → f_back → a's frame → f_back → module frame
When c() returns, its frame is discarded and execution resumes in b()’s frame. This chain is what Python walks when it generates a traceback after an exception.
Key Attributes
f_code — The code object this frame is executing. This gives you access to the function name, filename, and bytecode.
f_locals — A dictionary of local variable names and their current values. Note that this is a snapshot; in CPython, modifying this dictionary does not always change the actual local variables (though ctypes hacks can).
f_globals — The global namespace dictionary for the module where the function was defined.
f_back — The calling frame, or None if this is the bottom of the stack (the module-level frame).
f_lineno — The current line number being executed. Debuggers use this constantly.
f_lasti — The index of the last bytecode instruction executed. Combined with the code object’s bytecode, this tells you exactly where execution paused.
Accessing Frames at Runtime
The sys._getframe() function returns the current frame (or a caller’s frame if you pass a depth argument):
import sys
def who_called_me():
caller_frame = sys._getframe(1)
print(f"Called from {caller_frame.f_code.co_name} "
f"at line {caller_frame.f_lineno}")
def main():
who_called_me() # "Called from main at line 11"
The inspect module provides a cleaner interface with inspect.currentframe() and inspect.stack(), which returns a list of frame records from the current frame up to the top of the stack.
Frames and Debugging
Debuggers like pdb work by setting a trace function via sys.settrace(). Python calls this trace function at every line, function call, and return — passing the current frame each time. The debugger inspects f_lineno to show you where you are, reads f_locals to display variable values, and walks f_back to show the call stack.
Coverage tools work similarly — they record which lines are executed by checking f_lineno on each trace event.
Frames and Memory
Each frame holds references to its local variables, the global dictionary, and the code object. Frames that remain alive (for example, stored in an exception traceback or captured in a closure) keep all those references alive too. This is why long-lived tracebacks can sometimes cause memory leaks — they pin entire frames and everything those frames reference.
Python’s garbage collector handles frame cleanup, but being aware of this reference chain helps when debugging memory issues.
A Common Misconception
People sometimes think frames are created only for user-defined functions. In fact, every execution scope — the module level, class bodies, comprehensions, and generator iterations — gets a frame. Generators are special because their frames are suspended rather than destroyed, allowing execution to resume later with all local state intact.
One thing to remember: Frame objects are the runtime counterpart to code objects. While the code object is the static blueprint, the frame is the living workspace — tracking the current instruction, holding variable values, and linking to the caller. Understanding frames gives you the power to inspect and manipulate Python’s execution state while your program is running.
See Also
- Python Abstract Syntax Trees How Python reads your code like a recipe before cooking it — the hidden tree structure behind every program.
- Python Bytecode Manipulation How Python secretly translates your code into tiny instructions — and how you can peek at and change those instructions yourself.
- Python Code Objects Internals What Python actually creates when it reads your function — the hidden blueprint that tells the computer what to do.
- Python Compiler Pipeline The journey your Python code takes from text file to running program — explained like an assembly line.
- Python Peephole Optimizer How Python quietly tidies up your code behind the scenes — making it faster without you lifting a finger.