On Fri, May 29, 2020 at 04:52:38PM +0200, Dominik Vilsmeier wrote:
> Indeed locals are special, but why was it designed this way? Why not
> resolve such an unbound local name in the enclosing scopes?
Probably for speed.
When I first learned about the LGB rule (so long ago there wasn't even
an E for Enclosing!) I thought that Python *literally* did this:
- look for a local name 'x'
- if not found, look for a global name 'x'
- if not found, look for a builtin name 'x'
on every name lookup. But that's not what the interpreter actually does.
It has separate byte code instructions for fast local lookup, nonlocal
lookup, and global/builtin lookup. So the compiler needs to know at
compile-time which instruction to use.
Python might have used a single lookup which searched each scope in
turn, I believe that is (roughly) how Lua works. But that would
mean that within the same lexical block, a variable is sometimes global
and sometimes local:
def func():
# Inside this block we have:
print(x) # x is global
x = 1
print(x) # And now it's local.
and you can get that effect in Lua.
That's a perfectly logical and unambiguous rule, but it's probably not
very practical, especially if the function is large of if the assignment
is buried in a conditional.
def spam():
if condition:
x = 1
print(x) # Is x local or global?
And that's what happens for builtins and globals!
py> print(len) # builtin
<built-in function len>
py> len = 1
py> print(len) # global
1
py> del len
py> print(len) # builtin again
<built-in function len>
But inside a function, that's probably a Bad Thing.
We could require explicit declarations of scope for every variable, like
languages such as Pascal, C and Java use. But we don't.
What we have is a lexical rule for determining what scope names inside
functions belong to. Roughly something like this:
- every undotted name inside a function belongs to exactly one scope;
- if there is a global or nonlocal declaration, that takes precedence;
- otherwise, any binding operation to that variable anywhere in the
function, even in unreachable code, forces the variable to belong to
the local scope;
- otherwise, if there is an enclosing function, and there is a local
with that name in the enclosing function, then the name in the nested
function belongs to the enclosing scope;
- otherwise it's a global/builtin.
As a consequence of having scopes determined lexically, the compiler is
free to optimize for fast local lookups, and use different byte codes
for each lookup. In CPython 3.8:
LOAD_NAME # globals, builtins and class scope
LOAD_GLOBAL # globals and builtins
LOAD_DEREF # closures and nonlocals
LOAD_FAST # locals
LOAD_CONST # literals and certain other constants
Other implementations might not use the same lookup mechanisms, so long
as the keep the same semantics.
> It seems that there is no way to modify locals once the function is
> compiled (this is probably due to the fact that locals are optimized as
> a static array?). For example:
>
> >>> x = 1
> >>> def foo():
> ... exec('x = 2')
> ... print(x)
> ...
> >>> foo()
> 1
Not without byte-code hacking.
As you point out, in Python 2 it prints 2, not 1, because the
interpreter took extraordinary efforts to make it work that way. I don't
remember all the details but it was confusing and hardly anyone used it
except by accident (which made it even more confusing) and so it was
taken out in Python 3.
The reason it doesn't work in Python 3 isn't because of the static array
optimization. exec() could modify the static array (in CPython, it
doesn't, but it could). Jython has no such static array, but if and when
Jython 3 is available, it too should print 1 rather than 2.
The reason for Python's behaviour is that according to the lexical rule,
the lack of any assignment to x means that x is treated as a
global/builtin, not a local. Even if the exec() succeeded in creating a
local variable x with value 2, we have no way to tell the compiler to
look up x in the local scope. (Except by doing it manually.)
So hypothetically, this could work in a legal Python 3 interpreter:
x = "in the global scope"
def spam():
exec('x = "in the local scope"')
print(x)
print(locals()['x']
# prints
in the global scope
in the local scope
Although it doesn't work in CPython 3, it might work in some future
Jython 3 or other interpreters where modifications to locals() are
reflected in changes to local variables.
--
Steven
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/FVCXNBH2ID6F3T25Z33OTKU37GXR42WF/
Code of Conduct: http://python.org/psf/codeofconduct/