I work on TorchDynamo, PyTorch’s JIT compiler. Dynamo relies on the eval frame API (PEP 523), and one of the things it needs to do when a frame is traced is to read the frame’s local variables.
CPython has 3 public APIs for reading frame locals, but none of them are suitable to be used in Dynamo:
PyFrame_GetLocals(PyFrameObject*)PyFrame_GetVar(PyFrameObject*, PyObject*)PyEval_GetFrameLocals(void)
The first two take a PyFrameObject* as first argument and there’s no public way to get one from a _PyInterpreterFrame*. The last one (PyEval_GetFrameLocals) reads the current frame, and the frame might not be ready when Dynamo gets it.
So today PyTorch implements its own version of *_GetLocals, which relies on an interface that is fragile and hard to maintain: it requires defining Py_BUILD_CORE, uses internal CPython headers (Include/internal/pycore_*), copies CPython code (PyFrame_Clear, PyFrame_MakeAndSetFrameObject), and pokes at private struct fields such as frame->localsplus. As a result, each CPython update requires manual re-auditing and maintenance before PyTorch can support it.
I would like to propose a new PyUnstable_* function (PyUnstable_InterpreterFrame_GetLocals()), to provide downstream projects (namely PyTorch) a supported way to access interpreter-frame locals without reimplementing CPython’s internal frame-local logic.
There is precedent for this: the interpreter frame API already exposes 3 PyUnstable_InterpreterFrame_* accessors:
PyUnstable_InterpreterFrame_GetCode(_PyInterpreterFrame*)[ref]PyUnstable_InterpreterFrame_GetLasti(_PyInterpreterFrame*)[ref]PyUnstable_InterpreterFrame_GetLine(_PyInterpreterFrame*)[ref]
PyUnstable_InterpreterFrame_GetLocals(...) would be the natural fourth.
Spec
PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLocals(
struct _PyInterpreterFrame *frame, PyObject **out);
out is a caller-allocated array of exactly co_nlocalsplus PyObject* slots. On success, the function fills out[0 .. co_nlocalsplus) with the frame’s local variables indexed by localsplus index, and returns co_nlocalsplus. Every non-NULL slot holds a strong reference that the caller must release. A NULL means the slot has no value (an unbound, deleted, or hidden local). Cell and free variables are unboxed and the slots receive the cell’s contents, matching what locals() exposes.
On error, the function sets ValueError and returns -1. The only error condition is an invalid output buffer (out is NULL).
Example usage
A proposal of PyUnstable_InterpreterFrame_GetLocals was implemented in forks of CPython and PyTorch at: