Skip to content

Commit

Permalink
pythongh-74929: PEP 667 C API documentation (pythongh-119379)
Browse files Browse the repository at this point in the history
* Add docs for new APIs
* Add soft-deprecation notices
* Add What's New porting entries
* Update comments referencing `PyFrame_LocalsToFast()` to mention the proxy instead
* Other related cleanups found when looking for refs to the deprecated APIs
  • Loading branch information
ncoghlan authored and estyxx committed Jul 17, 2024
1 parent a6a5caf commit d290499
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 11 deletions.
38 changes: 38 additions & 0 deletions Doc/c-api/reflection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,30 @@ Reflection

.. c:function:: PyObject* PyEval_GetBuiltins(void)
.. deprecated:: 3.13
Use :c:func:`PyEval_GetFrameBuiltins` instead.
Return a dictionary of the builtins in the current execution frame,
or the interpreter of the thread state if no frame is currently executing.
.. c:function:: PyObject* PyEval_GetLocals(void)
.. deprecated:: 3.13
Use :c:func:`PyEval_GetFrameLocals` instead.
Return a dictionary of the local variables in the current execution frame,
or ``NULL`` if no frame is currently executing.
.. c:function:: PyObject* PyEval_GetGlobals(void)
.. deprecated:: 3.13
Use :c:func:`PyEval_GetFrameGlobals` instead.
Return a dictionary of the global variables in the current execution frame,
or ``NULL`` if no frame is currently executing.
Expand All @@ -31,6 +43,32 @@ Reflection
See also :c:func:`PyThreadState_GetFrame`.
.. c:function:: PyObject* PyEval_GetFrameBuiltins(void)
Return a dictionary of the builtins in the current execution frame,
or the interpreter of the thread state if no frame is currently executing.
.. versionadded:: 3.13
.. c:function:: PyObject* PyEval_GetFrameLocals(void)
Return a dictionary of the local variables in the current execution frame,
or ``NULL`` if no frame is currently executing. Equivalent to calling
:func:`locals` in Python code.
.. versionadded:: 3.13
.. c:function:: PyObject* PyEval_GetFrameGlobals(void)
Return a dictionary of the global variables in the current execution frame,
or ``NULL`` if no frame is currently executing. Equivalent to calling
:func:`globals` in Python code.
.. versionadded:: 3.13
.. c:function:: const char* PyEval_GetFuncName(PyObject *func)
Return the name of *func* if it is a function, class or instance object, else the
Expand Down
32 changes: 32 additions & 0 deletions Doc/data/refcounts.dat
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,12 @@ PyEval_GetGlobals:PyObject*::0:

PyEval_GetFrame:PyObject*::0:

PyEval_GetFrameBuiltins:PyObject*::+1:

PyEval_GetFrameLocals:PyObject*::+1:

PyEval_GetFrameGlobals:PyObject*::+1:

PyEval_GetFuncDesc:const char*:::
PyEval_GetFuncDesc:PyObject*:func:0:

Expand Down Expand Up @@ -916,6 +922,32 @@ PyFloat_FromString:PyObject*:str:0:
PyFloat_GetInfo:PyObject*::+1:
PyFloat_GetInfo::void::

PyFrame_GetBack:PyObject*::+1:
PyFrame_GetBack:PyFrameObject*:frame:0:

PyFrame_GetBuiltins:PyObject*::+1:
PyFrame_GetBuiltins:PyFrameObject*:frame:0:

PyFrame_GetCode:PyObject*::+1:
PyFrame_GetCode:PyFrameObject*:frame:0:

PyFrame_GetGenerator:PyObject*::+1:
PyFrame_GetGenerator:PyFrameObject*:frame:0:

PyFrame_GetGlobals:PyObject*::+1:
PyFrame_GetGlobals:PyFrameObject*:frame:0:

PyFrame_GetLocals:PyObject*::+1:
PyFrame_GetLocals:PyFrameObject*:frame:0:

PyFrame_GetVar:PyObject*::+1:
PyFrame_GetVar:PyFrameObject*:frame:0:
PyFrame_GetVar:PyObject*:name:0:

PyFrame_GetVarString:PyObject*::+1:
PyFrame_GetVarString:PyFrameObject*:frame:0:
PyFrame_GetVarString:const char*:name::

PyFrozenSet_Check:int:::
PyFrozenSet_Check:PyObject*:p:0:

Expand Down
16 changes: 15 additions & 1 deletion Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Interpreter improvements:
* :pep:`667`: The :func:`locals` builtin now has
:ref:`defined semantics <whatsnew313-locals-semantics>` when mutating the
returned mapping. Python debuggers and similar tools may now more reliably
update local variables in optimized frames even during concurrent code
update local variables in optimized scopes even during concurrent code
execution.

New typing features:
Expand Down Expand Up @@ -2143,6 +2143,11 @@ New Features
destruction the same way the :mod:`tracemalloc` module does. (Contributed
by Pablo Galindo in :gh:`93502`.)

* Add :c:func:`PyEval_GetFrameBuiltins`, :c:func:`PyEval_GetFrameGlobals`, and
:c:func:`PyEval_GetFrameLocals` to the C API. These replacements for
:c:func:`PyEval_GetBuiltins`, :c:func:`PyEval_GetGlobals`, and
:c:func:`PyEval_GetLocals` return :term:`strong references <strong reference>`
rather than borrowed references. (Added as part of :pep:`667`.)

Build Changes
=============
Expand Down Expand Up @@ -2318,6 +2323,15 @@ Changes in the C API
to :c:func:`PyUnstable_Code_GetFirstFree`.
(Contributed by Bogdan Romanyuk in :gh:`115781`.)

* :c:func:`!PyFrame_FastToLocals` and :c:func:`!PyFrame_FastToLocalsWithError`
no longer have any effect. Calling these functions has been redundant since
Python 3.11, when :c:func:`PyFrame_GetLocals` was first introduced.
(Changed as part of :pep:`667`.)

* :c:func:`!PyFrame_LocalsToFast` no longer has any effect. Calling this function
is redundant now that :c:func:`PyFrame_GetLocals` returns a write-through proxy
for :term:`optimized scopes <optimized scope>`. (Changed as part of :pep:`667`.)

Removed C APIs
--------------

Expand Down
13 changes: 9 additions & 4 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,15 @@ def test_dlopenflags(self):

@test.support.refcount_test
def test_refcount(self):
# n here must be a global in order for this test to pass while
# tracing with a python function. Tracing calls PyFrame_FastToLocals
# which will add a copy of any locals to the frame object, causing
# the reference count to increase by 2 instead of 1.
# n here originally had to be a global in order for this test to pass
# while tracing with a python function. Tracing used to call
# PyFrame_FastToLocals, which would add a copy of any locals to the
# frame object, causing the ref count to increase by 2 instead of 1.
# While that no longer happens (due to PEP 667), this test case retains
# its original global-based implementation
# PEP 683's immortal objects also made this point moot, since the
# refcount for None doesn't change anyway. Maybe this test should be
# using a different constant value? (e.g. an integer)
global n
self.assertRaises(TypeError, sys.getrefcount)
c = sys.getrefcount(None)
Expand Down
9 changes: 7 additions & 2 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1888,8 +1888,7 @@ frame_get_var(_PyInterpreterFrame *frame, PyCodeObject *co, int i,
}
// (likely) Otherwise it is an arg (kind & CO_FAST_LOCAL),
// with the initial value set when the frame was created...
// (unlikely) ...or it was set to some initial value by
// an earlier call to PyFrame_LocalsToFast().
// (unlikely) ...or it was set via the f_locals proxy.
}
}
}
Expand Down Expand Up @@ -2002,18 +2001,24 @@ PyFrame_GetVarString(PyFrameObject *frame, const char *name)
int
PyFrame_FastToLocalsWithError(PyFrameObject *f)
{
// Nothing to do here, as f_locals is now a write-through proxy in
// optimized frames. Soft-deprecated, since there's no maintenance hassle.
return 0;
}

void
PyFrame_FastToLocals(PyFrameObject *f)
{
// Nothing to do here, as f_locals is now a write-through proxy in
// optimized frames. Soft-deprecated, since there's no maintenance hassle.
return;
}

void
PyFrame_LocalsToFast(PyFrameObject *f, int clear)
{
// Nothing to do here, as f_locals is now a write-through proxy in
// optimized frames. Soft-deprecated, since there's no maintenance hassle.
return;
}

Expand Down
2 changes: 1 addition & 1 deletion Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1570,7 +1570,7 @@ dummy_func(

inst(MAKE_CELL, (--)) {
// "initial" is probably NULL but not if it's an arg (or set
// via PyFrame_LocalsToFast() before MAKE_CELL has run).
// via the f_locals proxy before MAKE_CELL has run).
PyObject *initial = GETLOCAL(oparg);
PyObject *cell = PyCell_New(initial);
if (cell == NULL) {
Expand Down
2 changes: 1 addition & 1 deletion Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Python/generated_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ Data members:
#include "pycore_sysmodule.h" // export _PySys_GetSizeOf()
#include "pycore_tuple.h" // _PyTuple_FromArray()

#include "frameobject.h" // PyFrame_FastToLocalsWithError()
#include "pydtrace.h" // PyDTrace_AUDIT()
#include "osdefs.h" // DELIM
#include "stdlib_module_names.h" // _Py_stdlib_module_names
Expand Down

0 comments on commit d290499

Please sign in to comment.