Skip to content

Commit 2180991

Browse files
authored
gh-118888: Further PEP 667 docs updates (gh-119893)
* Clarify impact on default behaviour of exec, eval, etc * Update documentation for changes to PyEval_GetLocals (gh-74929) Closes gh-11888
1 parent 3859e09 commit 2180991

File tree

2 files changed

+44
-3
lines changed

2 files changed

+44
-3
lines changed

Doc/c-api/reflection.rst

+19-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,24 @@ Reflection
1919
2020
.. deprecated:: 3.13
2121
22-
Use :c:func:`PyEval_GetFrameLocals` instead.
22+
To avoid creating a reference cycle in :term:`optimized scopes <optimized scope>`,
23+
use either :c:func:`PyEval_GetFrameLocals` to obtain the same behaviour as calling
24+
:func:`locals` in Python code, or else call :c:func:`PyFrame_GetLocals` on the result
25+
of :c:func:`PyEval_GetFrame` to get the same result as this function without having to
26+
cache the proxy instance on the underlying frame.
2327
24-
Return a dictionary of the local variables in the current execution frame,
28+
Return the :attr:`~frame.f_locals` attribute of the currently executing frame,
2529
or ``NULL`` if no frame is currently executing.
2630
31+
If the frame refers to an :term:`optimized scope`, this returns a
32+
write-through proxy object that allows modifying the locals.
33+
In all other cases (classes, modules, :func:`exec`, :func:`eval`) it returns
34+
the mapping representing the frame locals directly (as described for
35+
:func:`locals`).
36+
37+
.. versionchanged:: 3.13
38+
As part of :pep:`667`, return a proxy object for optimized scopes.
39+
2740
2841
.. c:function:: PyObject* PyEval_GetGlobals(void)
2942
@@ -57,6 +70,10 @@ Reflection
5770
or ``NULL`` if no frame is currently executing. Equivalent to calling
5871
:func:`locals` in Python code.
5972
73+
To access :attr:`~frame.f_locals` on the current frame without making an independent
74+
snapshot in :term:`optimized scopes <optimized scope>`, call :c:func:`PyFrame_GetLocals`
75+
on the result of :c:func:`PyEval_GetFrame`.
76+
6077
.. versionadded:: 3.13
6178
6279

Doc/whatsnew/3.13.rst

+25-1
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,21 @@ comprehensions, and generator expressions) to explicitly return independent
266266
snapshots of the currently assigned local variables, including locally
267267
referenced nonlocal variables captured in closures.
268268

269+
This change to the semantics of :func:`locals` in optimized scopes also affects the default
270+
behaviour of code execution functions that implicitly target ``locals()`` if no explicit
271+
namespace is provided (such as :func:`exec` and :func:`eval`). In previous versions, whether
272+
or not changes could be accessed by calling ``locals()`` after calling the code execution
273+
function was implementation dependent. In CPython specifically, such code would typically
274+
appear to work as desired, but could sometimes fail in optimized scopes based on other code
275+
(including debuggers and code execution tracing tools) potentially resetting the shared
276+
snapshot in that scope. Now, the code will always run against an independent snapshot of the
277+
local variables in optimized scopes, and hence the changes will never be visible in
278+
subsequent calls to ``locals()``. To access the changes made in these cases, an explicit
279+
namespace reference must now be passed to the relevant function. Alternatively, it may make
280+
sense to update affected code to use a higher level code execution API that returns the
281+
resulting code execution namespace (e.g. :func:`runpy.run_path` when executing Python
282+
files from disk).
283+
269284
To ensure debuggers and similar tools can reliably update local variables in
270285
scopes affected by this change, :attr:`FrameType.f_locals <frame.f_locals>` now
271286
returns a write-through proxy to the frame's local and locally referenced
@@ -2235,7 +2250,10 @@ Changes in the Python API
22352250
independent snapshot on each call, and hence no longer implicitly updates
22362251
previously returned references. Obtaining the legacy CPython behaviour now
22372252
requires explicit calls to update the initially returned dictionary with the
2238-
results of subsequent calls to ``locals()``. (Changed as part of :pep:`667`.)
2253+
results of subsequent calls to ``locals()``. Code execution functions that
2254+
implicitly target ``locals()`` (such as ``exec`` and ``eval``) must be
2255+
passed an explicit namespace to access their results in an optimized scope.
2256+
(Changed as part of :pep:`667`.)
22392257

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

2344+
* Calling :c:func:`PyFrame_GetLocals` or :c:func:`PyEval_GetLocals` in an
2345+
:term:`optimized scope` now returns a write-through proxy rather than a
2346+
snapshot that gets updated at ill-specified times. If a snapshot is desired,
2347+
it must be created explicitly (e.g. with :c:func:`PyDict_Copy`) or by calling
2348+
the new :c:func:`PyEval_GetFrameLocals` API. (Changed as part of :pep:`667`.)
2349+
23262350
* :c:func:`!PyFrame_FastToLocals` and :c:func:`!PyFrame_FastToLocalsWithError`
23272351
no longer have any effect. Calling these functions has been redundant since
23282352
Python 3.11, when :c:func:`PyFrame_GetLocals` was first introduced.

0 commit comments

Comments
 (0)