@@ -206,12 +206,11 @@ may not affect the values of local and free variables used by the interpreter."
206206This PEP proposes to change that text to instead say:
207207
208208 At function scope (including for generators and coroutines), [this function]
209- returns a
210- dynamic snapshot of the function's local variables and any nonlocal cell
211- references. In this case, changes made via the snapshot are *not * written
212- back to the corresponding local variables or nonlocal cell references, and
213- any such changes to the snapshot will be overwritten if the snapshot is
214- subsequently refreshed (e.g. by another call to ``locals() ``).
209+ returns a dynamic snapshot of the function's local variables and any
210+ nonlocal cell references. In this case, changes made via the snapshot are
211+ *not * written back to the corresponding local variables or nonlocal cell
212+ references, and any such changes to the snapshot will be overwritten if the
213+ snapshot is subsequently refreshed (e.g. by another call to ``locals() ``).
215214
216215 CPython implementation detail: the dynamic snapshot for the currently
217216 executing frame will be implicitly refreshed before each call to the trace
@@ -281,13 +280,15 @@ return a reference to the dynamic snapshot rather than to the write-through
281280proxy.
282281
283282At the C API layer, ``PyEval_GetLocals() `` will implement the same semantics
284- as the Python level ``locals() `` builtin, and a new ``PyFrame_GetLocals(frame) ``
285- accessor API will be provided to allow the proxy bypass logic to be encapsulated
286- entirely inside the frame implementation. The C level equivalent of accessing
287- ``pyframe.f_locals `` in Python will be to access ``cframe->f_locals `` directly
288- (the one difference is that accessing ``pyframe.f_locals `` will continue to
289- implicitly refresh the dynamic snapshot, whereas C code will need to explicitly
290- call ``PyFrame_GetLocals(frame) `` to refresh the snapshot).
283+ as the Python level ``locals() `` builtin, and a new
284+ ``PyFrame_GetPyLocals(frame) `` accessor API will be provided to allow the
285+ function level proxy bypass logic to be encapsulated entirely inside the frame
286+ implementation.
287+
288+ The C level equivalent of accessing ``pyframe.f_locals `` in Python will be a
289+ new ``PyFrame_GetLocalsAttr(frame) `` API. Like the Python level descriptor, the
290+ new API will implicitly refresh the dynamic snapshot at function scope before
291+ returning a reference to the write-through proxy.
291292
292293The ``PyFrame_LocalsToFast() `` function will be changed to always emit
293294``RuntimeError ``, explaining that it is no longer a supported operation, and
@@ -324,7 +325,59 @@ The proposal in this PEP aims to retain the first two properties (to maintain
324325backwards compatibility with as much code as possible) while ensuring that
325326simply installing a trace hook can't enable rebinding of function locals via
326327the ``locals() `` builtin (whereas enabling rebinding via
327- ``inspect.currentframe().f_locals `` is fully intended).
328+ ``frame.f_locals `` inside the tracehook implementation is fully intended).
329+
330+
331+ Keeping ``locals() `` as a dynamic snapshot at function scope
332+ ------------------------------------------------------------
333+
334+ It would theoretically be possible to change the semantics of the ``locals() ``
335+ builtin to return the write-through proxy at function scope, rather than
336+ continuing to return a dynamic snapshot.
337+
338+ This PEP doesn't (and won't) propose this as it's a backwards incompatible
339+ change in practice, even though code that relies on the current behaviour is
340+ technically operating in an undefined area of the language specification.
341+
342+ Consider the following code snippet::
343+
344+ def example():
345+ x = 1
346+ locals()["x"] = 2
347+ print(x)
348+
349+ Even with a trace hook installed, that function will consistently print ``1 ``
350+ on the current reference interpreter implementation::
351+
352+ >>> example()
353+ 1
354+ >>> import sys
355+ >>> def basic_hook(*args):
356+ ... return basic_hook
357+ ...
358+ >>> sys.settrace(basic_hook)
359+ >>> example()
360+ 1
361+
362+ Similarly, ``locals() `` can be passed to the ``exec() `` and ``eval() `` builtins
363+ at function scope without risking unexpected rebinding of local variables.
364+
365+ Provoking the reference interpreter into incorrectly mutating the local variable
366+ state requires a more complex setup where a nested function closes over a
367+ variable being rebound in the outer function, and due to the use of either
368+ threads, generators, or coroutines, it's possible for a trace function to start
369+ running for the nested function before the rebinding operation in the outer
370+ function, but finish running after the rebinding operation has taken place (in
371+ which case the rebinding will be reverted, which is the bug reported in [1 ]_).
372+
373+ In addition to preserving the de facto semantics which have been in place since
374+ PEP 227 introduced nested scopes in Python 2.1, the other benefit of restricting
375+ the write-through proxy support to the implementation-defined frame object API
376+ is that it means that only interpreter implementations which emulate the full
377+ frame API need to offer the write-through capability at all, and that
378+ JIT-compiled implementations only need to enable it when a frame introspection
379+ API is invoked, or a trace hook is installed, not whenever ``locals() `` is
380+ accessed at function scope.
328381
329382
330383What happens with the default args for ``eval() `` and ``exec() ``?
@@ -333,7 +386,7 @@ What happens with the default args for ``eval()`` and ``exec()``?
333386These are formally defined as inheriting ``globals() `` and ``locals() `` from
334387the calling scope by default.
335388
336- There doesn 't seem to be any reason for the PEP to change this .
389+ There isn 't any need for the PEP to change these defaults, so it doesn't .
337390
338391
339392Changing the frame API semantics in regular operation
0 commit comments