Statement local namespaces summary (was Re: python3: 'where' keyword)

Bengt Richter bokr at oz.net
Thu Jan 13 17:18:55 EST 2005


On Fri, 14 Jan 2005 01:48:48 +1000, Nick Coghlan <ncoghlan at iinet.net.au> wrote:

>Nick Coghlan wrote:
>> Semantics
>> ---------
>> The code::
>> 
>> <statement> with:
>>    <suite>
>> 
>> translates to::
>> 
>> def unique_name():
>>     <suite>
>>     <statement>
>> unique_name()
>
>I've come to the conclusion that these semantics aren't what I would expect from 
>the construct. Exactly what I would expect can't really be expressed in current 
>Python due to the way local name bindings work. The main thing to consider is 
>what one would expect the following to print:
>
>def f():
>     a = 1
>     b = 2
>     print 1, locals()
>     print 3, locals() using:
>         a = 2
>         c = 3
>         print 2, locals()
>     print 4, locals()
>
>I think the least suprising result would be:
>
>1 {'a': 1, 'b': 2}         # Outer scope
>2 {'a': 2, 'c': 3}         # Inner scope
>3 {'a': 2, 'b': 2, 'c': 3} # Bridging scope
>4 {'a': 1, 'b': 2}         # Outer scope
>
>In that arrangement, the statement with a using clause is executed normally in 
>the outer scope, but with the ability to see additional names in its local 
>namespace. If this can be arranged, then name binding in the statement with the 
>using clause will work as we want it to.
>
>Anyway, I think further investigation of the idea is dependent on a closer look 
>at the feasibility of actually implementing it. Given that it isn't as 
>compatible with the existing nested scope structure as I first thought, I 
>suspect it will be both tricky to implement, and hard to sell to the BDFL 
>afterwards :(
>
In the timbot's let/in format:

def f():
    a = 1
    b = 2
    print 1, locals()
    let:
        a = 2
        c = 3
        print 2, locals()
    in:
        print 3, locals()
    print 4, locals()

I think the effect would be as if

 >>> def f():
 ...     a = 1
 ...     b = 2
 ...     print 1, locals()
 ...     def __unique_temp():
 ...         a = 2
 ...         c = 3
 ...         print 2, locals()
 ...         def __unique_too():
 ...             print 3, locals()
 ...         __unique_too()
 ...     __unique_temp()
 ...     del __unique_temp
 ...     print 4, locals()
 ...
 >>> f()
 1 {'a': 1, 'b': 2}
 2 {'a': 2, 'c': 3}
 3 {}
 4 {'a': 1, 'b': 2}

print 3, locals() doesn't show a,b,c in locals() unless you use them
somehow in that scope, e.g.,

 >>> def f():
 ...     a = 1
 ...     b = 2
 ...     print 1, locals()
 ...     def __unique_temp():
 ...         a = 2
 ...         c = 3
 ...         print 2, locals()
 ...         def __unique_too():
 ...             print 3, locals(), (a,b,c) # force references for locals()
 ...         __unique_too()
 ...     __unique_temp()
 ...     del __unique_temp
 ...     print 4, locals()
 ...
 >>> f()
 1 {'a': 1, 'b': 2}
 2 {'a': 2, 'c': 3, 'b': 2}
 3 {'a': 2, 'c': 3, 'b': 2} (2, 2, 3)
 4 {'a': 1, 'b': 2}

Of course, locals() does not include globals, even though they're
referenced and visible:
 >>> b
 'global b'
 >>> def bar():
 ...     print locals(), b
 ...
 >>> bar()
 {} global b

The trouble with this is that bindings created in __unique_too all get thrown away,
and you wouldn't want that limitation. So I proposed specifying the (re)bindable names
in a parenthesized list with the let, like "let(k, q, w): ..." so that those names
would be (re)bindable in the same scope as the let(...): statement.

As an extension, I also proposed optionally binding __unique_temp to a specified name
and not calling it automatically, instead of the automatic call and del.

That provides new ways to factor updates to local namespaces into local functions
with selective write-through (bind/rebind) to local names. E.g.,

# define case blocks for switch
# better sugar later, this is to demo functinality ;-)
#a
let(x):
    k = 123
in foo:
    x = k
#b
let(x, y):
    q = 456
    from math import pi as r
in bar:
    x = q
    y=r # extra binding created if bar is called
#c
let(x):in baz:x=789  # most compact form, where nothing in the let clause

switch = dict(a=foo, b=bar, c=baz)

Now you can update local bindings with a case switch:
    x = 0
    case = 'b'
    print x  # => 0
    switch[case]()  # executes bar() in this example, which assigns local x=456 and y=pi
    print x  # => 456


This spare example is easy to dismiss, but think of foo, bar, and baz as arbitrary sequences of statements
in the local namespace, except you can factor them out as a single named group and invoke them
safely by name(), and have them affect only the local names you specify in the group's let(x, y, ...): spec.

It provides a new way of factoring. As well as things no one has thought of yet ;-)

The other thing to think about is that the let suite could be strictly def-time, which would
provide the opportunity to avoid re-calculating things in functions without abusing default args,
and using the easy closure-variable creation instead. E.g.,

let(foo):
    preset = big_calc()
in:
    def foo(x):
        return x * preset

(This "in:" has no "in xxx:" name, so the effect is immediate execution of the anonymously
defined function, which writes through to foo with the def, as permitted by let(foo):).

Problems? (Besides NIH, which I struggle with regularly, and had to overcome to accept Tim's
starting point in this ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list