From 49065591c567dffb7d20e3d0570962e0b1d4f8c8 Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Sun, 2 Jun 2024 14:10:09 +1000 Subject: [PATCH 1/3] gh-118934: Fix PyEval_GetLocals docs (PEP 667) PEP 667's description of the planned changes to PyEval_GetLocals was internally inconsistent when accepted, so the docs added for gh-74929 didn't match either the current behaviour or the intended behaviour once gh-118934 is fixed. This PR updates the documentation and 3.13 What's New to match the intended behaviour (once gh-118934 is fixed). --- Doc/c-api/reflection.rst | 27 ++++++++++++++++----------- Doc/reference/datamodel.rst | 4 ++-- Doc/whatsnew/3.13.rst | 33 +++++++++++++++++++++++++-------- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/Doc/c-api/reflection.rst b/Doc/c-api/reflection.rst index af9a1a74ec137e..038e6977104560 100644 --- a/Doc/c-api/reflection.rst +++ b/Doc/c-api/reflection.rst @@ -19,23 +19,28 @@ Reflection .. deprecated:: 3.13 - To avoid creating a reference cycle in :term:`optimized scopes `, - use either :c:func:`PyEval_GetFrameLocals` to obtain the same behaviour as calling + 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. + of :c:func:`PyEval_GetFrame` to access the :attr:`~frame.f_locals` attribute of the + currently executing frame. - Return the :attr:`~frame.f_locals` attribute of the currently executing frame, + Return a mapping providing access to the local variables in the current execution 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`). + Refer to :func:`locals` for details of the mapping returned at different scopes. + + As this function returns a :term:`borrowed reference`, the dictionary returned for + :term:`optimized scopes ` is cached on the frame object and will remain + alive as long as the frame object does. Unlike :c:func:`PyEval_GetFrameLocals` and + :func:`locals`, subsequent calls to this function in the same frame will update the + contents of the cached dictionary to reflect changes in the state of the local variables + rather than returning a new snapshot. .. versionchanged:: 3.13 - As part of :pep:`667`, return a proxy object for optimized scopes. + As part of :pep:`667`, :c:func:`PyFrame_GetLocals`, :func:`locals`, and + :attr:`FrameType.f_locals ` no longer make use of the shared cache + dictionary. Refer to the :ref:`What's New entry ` for + additional details. .. c:function:: PyObject* PyEval_GetGlobals(void) diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 134385ed2f1860..9110060a6177e5 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1347,13 +1347,13 @@ Special read-only attributes ``object.__getattr__`` with arguments ``obj`` and ``"f_code"``. * - .. attribute:: frame.f_locals - - The dictionary used by the frame to look up + - The mapping used by the frame to look up :ref:`local variables `. If the frame refers to an :term:`optimized scope`, this may return a write-through proxy object. .. versionchanged:: 3.13 - Return a proxy for functions and comprehensions. + Return a proxy for optimized scopes. * - .. attribute:: frame.f_globals - The dictionary used by the frame to look up diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index ab260bf2a2d740..6758dfffbeb2c1 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -287,8 +287,9 @@ returns a write-through proxy to the frame's local and locally referenced nonlocal variables in these scopes, rather than returning an inconsistently updated shared ``dict`` instance with undefined runtime semantics. -See :pep:`667` for more details, including related C API changes and -deprecations. +See :pep:`667` for more details, including related C API changes and deprecations. Porting +notes are also provided below for the affected :ref:`Python APIs ` +and :ref:`C APIs ` (PEP and implementation contributed by Mark Shannon and Tian Gao in :gh:`74929`. Documentation updates provided by Guido van Rossum and @@ -2246,6 +2247,8 @@ Changes in the Python API returned by :meth:`zipfile.ZipFile.open` was changed from ``'r'`` to ``'rb'``. (Contributed by Serhiy Storchaka in :gh:`115961`.) +.. _pep667-porting-notes-py: + * Calling :func:`locals` in an :term:`optimized scope` now produces an independent snapshot on each call, and hence no longer implicitly updates previously returned references. Obtaining the legacy CPython behaviour now @@ -2341,15 +2344,24 @@ 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`.) +.. _pep667-porting-notes-c: + +* The effects of mutating the dictionary returned from :c:func:`PyEval_GetLocals` in an + :term:`optimized scope` have changed. Changes made this way will now *only* be visible to + subsequent :c:func:`PyEval_GetLocals` calls in that frame, as :c:func:`PyFrame_GetLocals`, + :func:`locals`, and :attr:`FrameType.f_locals ` no longer access the same + underlying cached dictionary. The recommended code update depends on how the function was + being used, so refer to the deprecation notice on the function for details. + (Changed as part of :pep:`667`.) + +* Calling :c:func:`PyFrame_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. + Python 3.11, when :c:func:`PyFrame_GetLocals` was first introduced. (Changed as part of :pep:`667`.) * :c:func:`!PyFrame_LocalsToFast` no longer has any effect. Calling this function @@ -2509,6 +2521,11 @@ Deprecated C APIs :c:func:`PyWeakref_GetRef` on Python 3.12 and older. (Contributed by Victor Stinner in :gh:`105927`.) +* Deprecate the :c:func:`PyEval_GetBuiltins`, :c:func:`PyEval_GetGlobals`, and + :c:func:`PyEval_GetLocals` functions, which return a :term:`borrowed reference`. + Refer to the deprecation notices on each function for their recommended replacements. + (Soft deprecated as part of :pep:`667`.) + Pending Removal in Python 3.14 ------------------------------ From bb7b16fbda864e7ddbde8430c16a2b8e2e613be2 Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Sun, 2 Jun 2024 14:18:02 +1000 Subject: [PATCH 2/3] Add missing full stop --- Doc/whatsnew/3.13.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 6758dfffbeb2c1..ad1912b9c3f1d1 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -289,7 +289,7 @@ updated shared ``dict`` instance with undefined runtime semantics. See :pep:`667` for more details, including related C API changes and deprecations. Porting notes are also provided below for the affected :ref:`Python APIs ` -and :ref:`C APIs ` +and :ref:`C APIs `. (PEP and implementation contributed by Mark Shannon and Tian Gao in :gh:`74929`. Documentation updates provided by Guido van Rossum and From 97aac6fc80e5bc0695174686272156e627c4c311 Mon Sep 17 00:00:00 2001 From: Alyssa Coghlan Date: Sun, 2 Jun 2024 14:28:32 +1000 Subject: [PATCH 3/3] Only new dict entries are preserved --- Doc/whatsnew/3.13.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index ad1912b9c3f1d1..66626ac06428b9 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -2347,11 +2347,14 @@ Changes in the C API .. _pep667-porting-notes-c: * The effects of mutating the dictionary returned from :c:func:`PyEval_GetLocals` in an - :term:`optimized scope` have changed. Changes made this way will now *only* be visible to - subsequent :c:func:`PyEval_GetLocals` calls in that frame, as :c:func:`PyFrame_GetLocals`, - :func:`locals`, and :attr:`FrameType.f_locals ` no longer access the same - underlying cached dictionary. The recommended code update depends on how the function was - being used, so refer to the deprecation notice on the function for details. + :term:`optimized scope` have changed. New dict entries added this way will now *only* be + visible to subsequent :c:func:`PyEval_GetLocals` calls in that frame, as + :c:func:`PyFrame_GetLocals`, :func:`locals`, and + :attr:`FrameType.f_locals ` no longer access the same underlying cached + dictionary. Changes made to entries for actual variable names and names added via the + write-through proxy interfaces will be overwritten on subsequent calls to + :c:func:`PyEval_GetLocals` in that frame. The recommended code update depends on how the + function was being used, so refer to the deprecation notice on the function for details. (Changed as part of :pep:`667`.) * Calling :c:func:`PyFrame_GetLocals` in an :term:`optimized scope` now returns a