Skip to main content

Objects/frameobject.c

cpython 3.14 @ ab2d84fe1023/Objects/frameobject.c

Objects/frameobject.c defines PyFrameObject and the helpers that the eval loop uses to manage execution state. In Python 3.11+ the frame was split: an internal _PyInterpreterFrame holds the hot execution state on the C stack, while PyFrameObject is a lazily-allocated Python-visible wrapper.

Map

LinesSymbolRole
1-200PyFrameObject struct, _PyFrame_New_NoTrackFrame allocation and initialization
201-450PyFrame_LocalsToFast, PyFrame_FastToLocalsSync between fast locals array and f_locals dict
451-650frame_getlineno, PyFrame_GetLineNumberLine number from position table
651-900frame_getlocals, frame_getglobalsProperty accessors
901-1100Generator frame lifecycle_PyFrameObject_GetGenerator, frame suspension

Reading

Lazy PyFrameObject allocation

The hot _PyInterpreterFrame lives on the C stack or in the object's frame array. A PyFrameObject Python wrapper is only allocated on first access via sys._getframe(), traceback, or inspect. This avoids allocating a heap object for every function call.

// CPython: Objects/frameobject.c:44 _PyFrame_MakeAndSetFrameObject
PyFrameObject *
_PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame)
{
PyFrameObject *f = _PyObject_GC_New(&PyFrame_Type);
...
f->f_frame = frame;
frame->frame_obj = f;
return f;
}

PyFrame_FastToLocals

f_locals starts as NULL. When accessed, PyFrame_FastToLocals copies the fast-locals array (LOAD_FAST/STORE_FAST slots) into a dict. Edits to the dict are reflected back via PyFrame_LocalsToFast, which is called before certain LOAD_DEREF paths.

Line number computation

PyFrame_GetLineNumber calls _PyCode_InitAddressRange and scans the position table forward from the start of the code object to the current f_lasti offset. The result is the 1-based source line number for tracebacks.

Generator frame suspension

When a YIELD_VALUE instruction fires, the _PyInterpreterFrame is not freed; it is owned by the generator object (PyGenObject.gi_iframe). On the next __next__ call the frame is re-entered at f_lasti + 1.

gopy notes

vm/eval_gen.go manages generator frame suspension. The lazy PyFrameObject allocation pattern is not yet needed in gopy since sys._getframe is not yet implemented. Frame introspection for tracebacks is in vm/eval_unwind.go.