Skip to content

Commit

Permalink
gh-118888: Further PEP 667 docs updates (gh-119893)
Browse files Browse the repository at this point in the history
* Clarify impact on default behaviour of exec, eval, etc
* Update documentation for changes to PyEval_GetLocals (gh-74929)

Closes gh-11888
  • Loading branch information
ncoghlan authored Jun 1, 2024
1 parent 3859e09 commit 2180991
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 3 deletions.
21 changes: 19 additions & 2 deletions Doc/c-api/reflection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,24 @@ Reflection
.. deprecated:: 3.13
Use :c:func:`PyEval_GetFrameLocals` instead.
To avoid creating a reference cycle in :term:`optimized scopes <optimized scope>`,
use either :c:func:`PyEval_GetFrameLocals` to obtain the same behaviour as calling
:func:`locals` in Python code, or else call :c:func:`PyFrame_GetLocals` on the result
of :c:func:`PyEval_GetFrame` to get the same result as this function without having to
cache the proxy instance on the underlying frame.
Return a dictionary of the local variables in the current execution frame,
Return the :attr:`~frame.f_locals` attribute of the currently executing frame,
or ``NULL`` if no frame is currently executing.
If the frame refers to an :term:`optimized scope`, this returns a
write-through proxy object that allows modifying the locals.
In all other cases (classes, modules, :func:`exec`, :func:`eval`) it returns
the mapping representing the frame locals directly (as described for
:func:`locals`).
.. versionchanged:: 3.13
As part of :pep:`667`, return a proxy object for optimized scopes.
.. c:function:: PyObject* PyEval_GetGlobals(void)
Expand Down Expand Up @@ -57,6 +70,10 @@ Reflection
or ``NULL`` if no frame is currently executing. Equivalent to calling
:func:`locals` in Python code.
To access :attr:`~frame.f_locals` on the current frame without making an independent
snapshot in :term:`optimized scopes <optimized scope>`, call :c:func:`PyFrame_GetLocals`
on the result of :c:func:`PyEval_GetFrame`.
.. versionadded:: 3.13
Expand Down
26 changes: 25 additions & 1 deletion Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,21 @@ comprehensions, and generator expressions) to explicitly return independent
snapshots of the currently assigned local variables, including locally
referenced nonlocal variables captured in closures.

This change to the semantics of :func:`locals` in optimized scopes also affects the default
behaviour of code execution functions that implicitly target ``locals()`` if no explicit
namespace is provided (such as :func:`exec` and :func:`eval`). In previous versions, whether
or not changes could be accessed by calling ``locals()`` after calling the code execution
function was implementation dependent. In CPython specifically, such code would typically
appear to work as desired, but could sometimes fail in optimized scopes based on other code
(including debuggers and code execution tracing tools) potentially resetting the shared
snapshot in that scope. Now, the code will always run against an independent snapshot of the
local variables in optimized scopes, and hence the changes will never be visible in
subsequent calls to ``locals()``. To access the changes made in these cases, an explicit
namespace reference must now be passed to the relevant function. Alternatively, it may make
sense to update affected code to use a higher level code execution API that returns the
resulting code execution namespace (e.g. :func:`runpy.run_path` when executing Python
files from disk).

To ensure debuggers and similar tools can reliably update local variables in
scopes affected by this change, :attr:`FrameType.f_locals <frame.f_locals>` now
returns a write-through proxy to the frame's local and locally referenced
Expand Down Expand Up @@ -2235,7 +2250,10 @@ Changes in the Python API
independent snapshot on each call, and hence no longer implicitly updates
previously returned references. Obtaining the legacy CPython behaviour now
requires explicit calls to update the initially returned dictionary with the
results of subsequent calls to ``locals()``. (Changed as part of :pep:`667`.)
results of subsequent calls to ``locals()``. Code execution functions that
implicitly target ``locals()`` (such as ``exec`` and ``eval``) must be
passed an explicit namespace to access their results in an optimized scope.
(Changed as part of :pep:`667`.)

* Calling :func:`locals` from a comprehension at module or class scope
(including via ``exec`` or ``eval``) once more behaves as if the comprehension
Expand Down Expand Up @@ -2323,6 +2341,12 @@ Changes in the C API
to :c:func:`PyUnstable_Code_GetFirstFree`.
(Contributed by Bogdan Romanyuk in :gh:`115781`.)

* Calling :c:func:`PyFrame_GetLocals` or :c:func:`PyEval_GetLocals` in an
:term:`optimized scope` now returns a write-through proxy rather than a
snapshot that gets updated at ill-specified times. If a snapshot is desired,
it must be created explicitly (e.g. with :c:func:`PyDict_Copy`) or by calling
the new :c:func:`PyEval_GetFrameLocals` API. (Changed as part of :pep:`667`.)

* :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.
Expand Down

0 comments on commit 2180991

Please sign in to comment.