Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-74929: PEP 667 C API documentation #119379

Merged
merged 7 commits into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 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 @@ -30,6 +42,31 @@ Reflection

See also :c:func:`PyThreadState_GetFrame`.

.. c:function:: PyObject* PyEval_GetFrameBuiltins(void)
ncoghlan marked this conversation as resolved.
Show resolved Hide resolved

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:`globals` in Python code.
ncoghlan marked this conversation as resolved.
Show resolved Hide resolved

.. 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:`locals` in Python code.

.. versionadded:: 3.13


.. c:function:: const char* PyEval_GetFuncName(PyObject *func)

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
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2128,6 +2128,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`)
ncoghlan marked this conversation as resolved.
Show resolved Hide resolved

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

* :c:func:`!PyFrame_FastToLocals`, :c:func:`!PyFrame_FastToLocalsWithError`, and
:c:func:`!PyFrame_LocalsToFast` no longer have any effect. Note that calling
them has been redundant since Python 3.11, when :c:func:`PyFrame_GetLocals`
was first introduced. (Changed as part of :pep:`667`.)
ncoghlan marked this conversation as resolved.
Show resolved Hide resolved

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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this "set externally" loophole still exists, it's just accessed slightly differently now.

}
}
}
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 @@ -1553,7 +1553,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
Loading