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/

Reply via email to