[Python-ideas] PEP 572: Statement-Local Name Bindings

Paul Moore p.f.moore at gmail.com
Wed Feb 28 15:28:30 EST 2018


On 28 February 2018 at 19:41, Chris Angelico <rosuav at gmail.com> wrote:
> On Thu, Mar 1, 2018 at 3:30 AM, Paul Moore <p.f.moore at gmail.com> wrote:

> Here's another equally untested piece of code:
>
> [(-b + (sqrt(b*b - 4*a*c) as disc)) / (2*a), (-b - disc) / (2*a)]

Yeah, that's a good real world example. And IMO it hides the symmetry
between the two expressions, so for me it's a good example of a case
where there's a readability *disadvantage* with the proposed syntax.

> I haven't dug into the implementation consequences of any of this at
> global scope. I know what parts of the code I need to look at, but my
> days have this annoying habit of having only 24 hours in them. Anyone
> got a bugfix for that? :|

I can give you daylight saving time and a few leap seconds, but that's
more of a workaround than a fix :-)

>> Consider
>>
>> x = 12
>> if (1 as x) == 1:
>>     def foo():
>>         print(x)
>>         # Actually, you'd probably get a "Name used before definition"
>> error here.
>>         # Would "global x" refer to x=12 or to the statement-local x (1)?
>>         # Would "nonlocal x" refer to the statement-local x?
>>         x = 13
>>         return x
>>     print(foo())
>
> Yeah, that's going to give UnboundLocalError, because inside foo(), x
> has been flagged as local. That's independent of the global scope
> changes.
>
> I'd like to say that "global x" would catch the 12, but until I
> actually get around to implementing it, I'm not sure.
>
>> print(x)
>> print(foo())
>> print(x)
>
> Anything that executes after the 'if' exits should see x as 12. The
> temporary variable is completely gone at that point.

So simplifying

x = 12
if (1 as x) == 1:
    def foo():
        return x
    print(foo())
print(foo())

prints

1
12

Personally, I can't work out how to think about that in a way that
isn't confusing :-(

I gather from what you've said elsewhere that your current
implementation uses a hidden name-mangled variable. IIRC, list
comprehensions used something similar in the original implementation,
but it resulted in weird consequences, and ultimately the
implementation switched to a "proper" scoped semantics and
implementation. I've no particular reason to think your implementation
might suffer in the same way, but understanding the semantics in terms
of an "assignment to a hidden variable" bothers me.

>> The most charitable thing I can say here is that the semantics are
>> currently under-specified in the PEP :-)
>
> Hah. This is why I started out by saying that this ONLY applies inside
> a function. Extending this to global scope (and class scope; my guess
> is that it'll behave the same as global) is something that I'm only
> just now looking into.

And yet, "only works within a function" is IMO an unacceptable
limitation - so we need to look into the implications here.

> So if, in the "in" expression list, you capture something, that thing
> will be visible all through the suite. Here's an example:
>
> for item in (get_items() as items):
>     print(item)
>     print(items)
> print(items)
>
> What actually happens is kinda this:
>
> for item in (get_items() as items_0x142857):
>     print(item)
>     print(items_0x142857)
> del items_0x142857
> print(items)

OK, so that explains why trying to write a closure over a variable
introduced via "as" won't work. But I wouldn't want such a renaming to
be mandated by the semantics, and I don't know how I'd explain the
(implied) behaviour without insisting on a name mangling
implementation, so I'm stuck here.

>> "What the current proof of concept implementation does" isn't useful
>> anyway, but even ignoring that I'd prefer to see what it *does* rather
>> than what it *compiles to*. But what needs to be documented is what
>> the PEP *proposes* it does.
>
> The current implementation matches my proposed semantics, as long as
> the code in question is all inside a function.

Understood. But I'd like the PEP to fully explain more of the intended
semantics, *without* referring to the specific implementation.
Remember, PyPy, Jython, IronPython, Cython, etc, will all have to
implement it too.

>> But I can even break expression local names:
>>
>>     x = ((lambda: boom()) as boom)
>>     x()
>>
>> It's possible that the "boom" is just my head exploding, not the
>> interpreter. But I think I just demonstrated a closure over an
>> expression-local name. For added fun, replace "x" with "boom"...
>
> And this is why I am not trying for expression-local names. If someone
> wants to run with a competing proposal for list-comprehension-local
> names, sure, but I'm not in favour of that either. Expression-local is
> too narrow to be useful AND it still has the problems that
> statement-local has.

I've no idea how that would work under statement-local names either, though.

    boom = lambda: boom()
    boom()

is just an infinite recursion. I'm less sure that the as version is.
Or the alternative form

    ((lambda: boom()) as boom)()

I know you can tell me what the implementation does - but I can't
reason it out from the spec.

>>> Thanks for the feedback! Keep it coming! :)
>>
>> Ask and you shall receive :-)
>
> If I take a razor and cut myself with it, it's called "self-harm" and
> can get me dinged for a psych evaluation. If I take a mailing list and
> induce it to send me hundreds of emails and force myself to read them
> all... there's probably a padded cell with my name on it somewhere.
>
> You know, I'd be okay with that actually. Just as long as the cell has wifi.

lol. Have fun :-)

Paul


More information about the Python-ideas mailing list