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.

pythoncompiler-internalslanguage-implementation

See Also