Nick Coghlan added the comment:

Since "soonish" turned out to be "4 years and counting", copying in the 
specifics of the proposal in from the old python-dev thread:

1. While nominally undefined, in practice lots of Python programs depend on the 
locals() builtin behaving exactly how it behaves in CPython.

2. PyPy at least has replicated that behaviour faithfully (to the extent of 
replicating our weird trace function related misbehaviour, as recently pointed 
out in issue #30744)

3. For module scopes and class scopes (and the corresponding forms of exec and 
eval), the expected behaviour is relatively straightforward to both define and 
implement:

* at module scope, as well as when using exec() or eval() with a
single namespace, locals() must return the same thing as globals(),
which must be the actual execution namespace. Subsequent execution may
change the contents of the returned mapping, and changes to the
returned mapping must change the execution environment.

* at class scope, as well as when using exec() or eval() with separate
global and local namespaces, locals() must return the specified local
namespace (which may be supplied by the metaclass __prepare__ method
in the case of classes). Subsequent execution may change the contents
of the returned mapping, and changes to the returned mapping must
change the execution environment. For classes, this mapping will not
be used as the actual class namespace underlying the defined class
(the class creation process will copy the contents to a fresh
dictionary that is only accessible by going through the class
machinery).

4. For function scopes, the appropriate semantics are less clear, as what 
CPython currently does is fairly weird and quirky.

* actual execution uses the fast locals array and cell references (for nonlocal 
variables)
* there's a PyFrame_FastToLocals operation that populates the frame's 
"f_locals" attribute based on the current state of the fast locals array and 
any referenced cells
* a direct reference to f_locals is returned from locals(), so if you hand out 
multiple concurrent references, then all those references will be to the exact 
same dictionary
* the two common calls to the reverse operation, PyFrame_LocalsToFast, were 
removed in the migration to Python 3: exec is no longer a statement and hence 
can longer affect function local namespaces, and the compiler now disallows the 
use of "from module import *" operations at function scope
* however, two obscure calling paths remain: PyFrame_LocalsToFast is called as 
part of returning from a trace function, and you can also still inject the 
IMPORT_STAR opcode when creating a function directly from a code object rather 
than via the compiler

It would be a lot simpler to document the expected behaviour at function scope 
if locals() were to be updated to return a true snapshot (i.e. a copy of 
f_locals, rather than a direct reference), with direct access to the shared 
locals reference requiring going through the frame attribute. That way, trace 
functions could still modify local variables (since they use `frame.f_locals`), 
but setting a trace function wouldn't suddenly have the side effect of making 
modifications to locals() take effect at function scope.

----------

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue17960>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to