Skip to content

Commit

Permalink
PEP 667: Clarify impact on PyEval_GetLocals
Browse files Browse the repository at this point in the history
See Implementation Notes in the updated PEP for details
of the correction/clarification and the rationale for it.

The discrepancy was detected when writing the documentation for
python/cpython#74929 and when reviewing
python/cpython#118934
  • Loading branch information
ncoghlan committed Jun 2, 2024
1 parent 7d0084f commit 78d16e3
Showing 1 changed file with 55 additions and 5 deletions.
60 changes: 55 additions & 5 deletions peps/pep-0667.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ The ``locals()`` function will act the same as it does now for class
and modules scopes. For function scopes it will return an instantaneous
snapshot of the underlying ``frame.f_locals``.


Implementation Notes
====================

When accepted, the PEP text suggested that ``PyEval_GetLocals`` would start returning a
cached instance of the new write-through proxy, while the implementation sketch indicated
it would continue to return a dictionary snapshot cached on the frame instance. This
discrepancy was identified while implementing the PEP, and resolved in favour of retaining
the Python 3.12 behaviour of returning a dictionary snapshot cached on the frame instance.

To avoid confusion when following the reference link from the Python 3.13 What's New
documentation, the PEP text has been updated accordingly.


Motivation
==========

Expand Down Expand Up @@ -169,8 +183,10 @@ The following functions should be used instead::

which return new references.

The semantics of ``PyEval_GetLocals()`` is changed as it now returns a
proxy for the frame locals in optimized frames, not a dictionary.
The semantics of ``PyEval_GetLocals()`` are technically unchanged, but they do change in
practice as the dictionary cached on optimized frames is no longer shared with other
mechanisms for accessing the frame locals (``locals()`` builtin, ``PyFrame_GetLocals``
function, frame ``f_locals`` attributes).

The following three functions will become no-ops, and will be deprecated::

Expand Down Expand Up @@ -207,9 +223,27 @@ C-API
PyEval_GetLocals
''''''''''''''''

Because ``PyEval_GetLocals()`` returns a borrowed reference, it requires
the proxy mapping to be cached on the frame, extending its lifetime and
creating a cycle. ``PyEval_GetFrameLocals()`` should be used instead.
``PyEval_GetLocals()`` has never historically distinguished between whether it was
emulating ``locals()`` or ``sys._getframe().f_locals`` at the Python level, as they all
returned references to the same shared cache of the local variable bindings.

With this PEP, ``locals()`` changes to return independent snapshots on each call for
optimized frames, and ``frame.f_locals`` (along with ``PyFrame_GetLocals``) changes to
return new write-through proxy instances.

Because ``PyEval_GetLocals()`` returns a borrowed reference, it isn't possible to update
its semantics to align with either of those alternatives, leaving it as the only remaining
API that requires a shared cache dictionary stored on the frame object.

While this technically leaves the semantics of the function unchanged, it no longer allows
extra dict entries to be made visible to users of the other APIs, as those APIs are no longer
accessing the same underlying cache dictionary.

Accordingly, the function will be marked as deprecated (with no specific timeline for
removal) and alternatives recommended as described below.

When ``PyEval_GetLocals()`` is being used as an equivalent to the Python ``locals()``
builtin, ``PyEval_GetFrameLocals()`` should be used instead.

This code::

Expand All @@ -226,6 +260,22 @@ should be replaced with::
goto error_handler;
}

When ``PyEval_GetLocals()`` is being used as an equivalent to calling
``sys._getframe().f_locals`` in Python, it should be replaced by calling
``PyFrame_GetLocals()`` on the result of ``PyEval_GetFrame()``.

In these cases, the original code should be replaced with::

frame = PyEval_GetFrame();
if (frame == NULL) {
goto error_handler;
}
locals = PyFrame_GetLocals(frame);
if (locals == NULL) {
goto error_handler;
}


Implementation
==============

Expand Down

0 comments on commit 78d16e3

Please sign in to comment.