Skip to main content

Objects/frameobject.c (part 2)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/frameobject.c

This annotation covers the Python-visible frame object. See objects_frameobject_detail for the C-level _PyInterpreterFrame layout.

Map

LinesSymbolRole
1-80PyFrame_NewCreate a PyFrameObject (Python-visible wrapper)
81-200frame_getlocalsframe.f_locals — materialize from fast locals array
201-300frame_setlinenoframe.f_lineno = N — for debugger line stepping
301-450_PyFrame_FastToLocalsCopy fast locals array → f_locals dict
451-600_PyFrame_LocalsToFastCopy f_locals dict → fast locals array (after debugger modification)
601-700frame_clearDecref all fast locals; called at frame deallocation

Reading

Lazy PyFrameObject creation

// CPython: Objects/frameobject.c:20 comment
/* PyFrameObject is the Python-level frame object (accessible via
sys._getframe(), traceback.tb_frame, etc.).
In 3.11+, the hot path uses _PyInterpreterFrame (C stack or generator).
PyFrameObject is created lazily when Python code accesses f_* attributes. */

frame_getlocals

// CPython: Objects/frameobject.c:120 frame_getlocals
static PyObject *
frame_getlocals(PyFrameObject *f, void *Py_UNUSED(ignored))
{
/* Build or update f_locals dict from fast locals array */
if (PyFrame_FastToLocalsWithError((PyFrameObject *)f) < 0)
return NULL;
return Py_NewRef(f->f_frame->f_locals);
}

f.f_locals returns a snapshot. Modifying it does not affect the actual local variables unless _PyFrame_LocalsToFast is called (which debuggers do).

frame_setlineno

// CPython: Objects/frameobject.c:230 frame_setlineno
static int
frame_setlineno(PyFrameObject *f, PyObject *p_new_lineno, void *Py_UNUSED(ignored))
{
/* Used by debuggers (pdb, coverage.py) to jump to a different line.
Validates that the target line is within the code object. */
int new_lineno = PyLong_AsInt(p_new_lineno);
...
/* Rewind prev_instr to the first instruction on new_lineno */
f->f_frame->prev_instr = target_instr;
return 0;
}

_PyFrame_FastToLocals

// CPython: Objects/frameobject.c:340 _PyFrame_FastToLocals
int
_PyFrame_FastToLocalsWithError(PyFrameObject *f)
{
PyObject **fast = f->f_frame->localsplus;
PyCodeObject *co = (PyCodeObject *)f->f_frame->f_executable;
Py_ssize_t j = co->co_nlocalsplus;
PyObject *locals = f->f_frame->f_locals;
if (locals == NULL) {
locals = f->f_frame->f_locals = PyDict_New();
}
for (Py_ssize_t i = 0; i < j; i++) {
PyObject *v = fast[i];
if (v != NULL)
PyDict_SetItem(locals, name_of_slot(co, i), v);
else
PyDict_DelItem(locals, name_of_slot(co, i));
}
return 0;
}

frame_clear

// CPython: Objects/frameobject.c:640 frame_clear
static void
frame_tp_clear(PyFrameObject *f)
{
/* Called when the frame object is collected.
Decref all objects in fast locals array. */
PyObject **fast = f->f_frame->localsplus;
Py_ssize_t n = f->f_frame->f_executable ?
((PyCodeObject *)f->f_frame->f_executable)->co_nlocalsplus : 0;
for (Py_ssize_t i = 0; i < n; i++) {
Py_CLEAR(fast[i]);
}
}

gopy notes

PyFrameObject is objects.PyFrameObject in objects/frameobject.go. frame_getlocals calls vm.FastToLocals in vm/frame.go. frame_setlineno is implemented but restricted to lines within the code object's range. _PyFrame_LocalsToFast is vm.LocalsToFast.