diff --git a/peps/pep-0667.rst b/peps/pep-0667.rst index cbe1b5d2640..2912c3b2de2 100644 --- a/peps/pep-0667.rst +++ b/peps/pep-0667.rst @@ -33,6 +33,22 @@ 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 by the Steering Council `__ +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 ========== @@ -169,8 +185,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:: @@ -207,9 +225,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:: @@ -226,6 +262,23 @@ 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); + frame = NULL; // Minimise visibility of borrowed reference + if (locals == NULL) { + goto error_handler; + } + + Implementation ==============