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 -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/FVCXNBH2ID6F3T25Z33OTKU37GXR42WF/ Code of Conduct: http://python.org/psf/codeofconduct/