Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
19b93fc
PEP 590: Use size_t for "number of arguments + flag"
encukou May 21, 2019
65b57e3
Have PyVectorcall_NARGS return Py_ssize_t again
encukou May 21, 2019
44f2986
PEP 558: Remove dynamic frame semantics proposal (#1051)
ncoghlan May 21, 2019
933fbf8
PEP 558: Note compatibility constraints on locals(), other updates (#…
ncoghlan May 21, 2019
2d53689
PEP 594: Update 1.5 (#1068)
tiran May 21, 2019
fdfece5
Keep colorsys (#1070)
tiran May 21, 2019
d6146f5
PEP 594: asyncore and asynchat are deprecated since 3.6 (#1071)
tiran May 21, 2019
2feb1d4
PEP 594: track experts (#1077)
tiran May 22, 2019
dbb8cfc
PEP 594: discussion to discuss.python.org (#1078)
tiran May 22, 2019
cd524fa
PEP 590: Record BDFL delegate (GH-1079)
encukou May 22, 2019
eb6282b
Change specification for `# type: ignore` (#1072)
JelleZijlstra May 22, 2019
17b1fc5
PEP 595: Improving bugs.python.org (#1083)
ezio-melotti May 23, 2019
74f0fb2
PEP 544: Couple final edits (#1084)
ilevkivskyi May 24, 2019
fcdd72d
PEP 587 version 5 (#1086)
vstinner May 24, 2019
7e4e284
PEP 587: Fix a few minor issues (#1087)
vstinner May 25, 2019
b3b34a2
PEP 594: Fix a couple of typos. (#1075)
Nightblade May 26, 2019
7958a1c
PEP 594: Fix typos (#1081)
matrixise May 26, 2019
8329a34
PEP 594: miswording fix (#1074)
jjbankert May 26, 2019
a6c96ca
Mark PEPs 544, 586, 589, and 591 as accepted for Python 3.8 (#1088)
ilevkivskyi May 26, 2019
a9e0d3c
PEP 574: Mark final (GH-1089)
pitrou May 27, 2019
8470f20
PEP 594: Update 2 (#1090)
tiran May 28, 2019
8363d1f
PEP 590: Mark the main API as private to allow changes in Python 3.9 …
encukou May 28, 2019
2918f57
Merge PR GH-1066 into master, fixing conflict
encukou May 28, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
PEP 558: Note compatibility constraints on locals(), other updates (#…
…1069)

- new design discussion section to cover the requirement that the
  semantics of locals() itself at function scope be left alone
- propose a C level API that exactly matches Python level
  frame.f_locals semantics
- other minor text formatting and wording updates
  • Loading branch information
ncoghlan authored May 21, 2019
commit 933fbf86263fe7a68f9fd72b7d851f6d80496740
83 changes: 68 additions & 15 deletions pep-0558.rst
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,11 @@ may not affect the values of local and free variables used by the interpreter."
This PEP proposes to change that text to instead say:

At function scope (including for generators and coroutines), [this function]
returns a
dynamic snapshot of the function's local variables and any nonlocal cell
references. In this case, changes made via the snapshot are *not* written
back to the corresponding local variables or nonlocal cell references, and
any such changes to the snapshot will be overwritten if the snapshot is
subsequently refreshed (e.g. by another call to ``locals()``).
returns a dynamic snapshot of the function's local variables and any
nonlocal cell references. In this case, changes made via the snapshot are
*not* written back to the corresponding local variables or nonlocal cell
references, and any such changes to the snapshot will be overwritten if the
snapshot is subsequently refreshed (e.g. by another call to ``locals()``).

CPython implementation detail: the dynamic snapshot for the currently
executing frame will be implicitly refreshed before each call to the trace
Expand Down Expand Up @@ -281,13 +280,15 @@ return a reference to the dynamic snapshot rather than to the write-through
proxy.

At the C API layer, ``PyEval_GetLocals()`` will implement the same semantics
as the Python level ``locals()`` builtin, and a new ``PyFrame_GetLocals(frame)``
accessor API will be provided to allow the proxy bypass logic to be encapsulated
entirely inside the frame implementation. The C level equivalent of accessing
``pyframe.f_locals`` in Python will be to access ``cframe->f_locals`` directly
(the one difference is that accessing ``pyframe.f_locals`` will continue to
implicitly refresh the dynamic snapshot, whereas C code will need to explicitly
call ``PyFrame_GetLocals(frame)`` to refresh the snapshot).
as the Python level ``locals()`` builtin, and a new
``PyFrame_GetPyLocals(frame)`` accessor API will be provided to allow the
function level proxy bypass logic to be encapsulated entirely inside the frame
implementation.

The C level equivalent of accessing ``pyframe.f_locals`` in Python will be a
new ``PyFrame_GetLocalsAttr(frame)`` API. Like the Python level descriptor, the
new API will implicitly refresh the dynamic snapshot at function scope before
returning a reference to the write-through proxy.

The ``PyFrame_LocalsToFast()`` function will be changed to always emit
``RuntimeError``, explaining that it is no longer a supported operation, and
Expand Down Expand Up @@ -324,7 +325,59 @@ The proposal in this PEP aims to retain the first two properties (to maintain
backwards compatibility with as much code as possible) while ensuring that
simply installing a trace hook can't enable rebinding of function locals via
the ``locals()`` builtin (whereas enabling rebinding via
``inspect.currentframe().f_locals`` is fully intended).
``frame.f_locals`` inside the tracehook implementation is fully intended).


Keeping ``locals()`` as a dynamic snapshot at function scope
------------------------------------------------------------

It would theoretically be possible to change the semantics of the ``locals()``
builtin to return the write-through proxy at function scope, rather than
continuing to return a dynamic snapshot.

This PEP doesn't (and won't) propose this as it's a backwards incompatible
change in practice, even though code that relies on the current behaviour is
technically operating in an undefined area of the language specification.

Consider the following code snippet::

def example():
x = 1
locals()["x"] = 2
print(x)

Even with a trace hook installed, that function will consistently print ``1``
on the current reference interpreter implementation::

>>> example()
1
>>> import sys
>>> def basic_hook(*args):
... return basic_hook
...
>>> sys.settrace(basic_hook)
>>> example()
1

Similarly, ``locals()`` can be passed to the ``exec()`` and ``eval()`` builtins
at function scope without risking unexpected rebinding of local variables.

Provoking the reference interpreter into incorrectly mutating the local variable
state requires a more complex setup where a nested function closes over a
variable being rebound in the outer function, and due to the use of either
threads, generators, or coroutines, it's possible for a trace function to start
running for the nested function before the rebinding operation in the outer
function, but finish running after the rebinding operation has taken place (in
which case the rebinding will be reverted, which is the bug reported in [1]_).

In addition to preserving the de facto semantics which have been in place since
PEP 227 introduced nested scopes in Python 2.1, the other benefit of restricting
the write-through proxy support to the implementation-defined frame object API
is that it means that only interpreter implementations which emulate the full
frame API need to offer the write-through capability at all, and that
JIT-compiled implementations only need to enable it when a frame introspection
API is invoked, or a trace hook is installed, not whenever ``locals()`` is
accessed at function scope.


What happens with the default args for ``eval()`` and ``exec()``?
Expand All @@ -333,7 +386,7 @@ What happens with the default args for ``eval()`` and ``exec()``?
These are formally defined as inheriting ``globals()`` and ``locals()`` from
the calling scope by default.

There doesn't seem to be any reason for the PEP to change this.
There isn't any need for the PEP to change these defaults, so it doesn't.


Changing the frame API semantics in regular operation
Expand Down