[Python-ideas] If branch merging

Andrew Barnert abarnert at yahoo.com
Wed Jun 10 03:58:15 CEST 2015

On Jun 9, 2015, at 17:54, Chris Angelico <rosuav at gmail.com> wrote:
> On Wed, Jun 10, 2015 at 10:20 AM, Andrew Barnert via Python-ideas
> <python-ideas at python.org> wrote:
>> Now, for implementation: any statement that contains an as expression anywhere is compiled to a function definition and a call to that function. The only trick is that any free variables have to be compiled as nonlocals in the inner function and as captured locals in the real function. (This trick doesn't have to apply to lambdas or comprehensions, because they can't have assignment statements inside them, but a while statement can.) I believe this scales to nested statements with as-bindings, and to as-bindings inside explicit local functions and vice-versa.
> I'd actually rather see this implemented the other way around: instead
> of turning this into a function call, actually have a real concept of
> nested scoping. Nested functions imply changes to tracebacks and such,
> which scoping doesn't require.
> How hard would it be to hack the bytecode compiler to treat two names
> as distinct despite appearing the same?

Here's a quick&dirty idea that might work: Basically, just gensyn a name like .0 for the second e (as is done for comprehensions), compile as normal, then rename the .0 back to e in the code attributes.

The problem is how to make this interact with all kinds of other stuff. What if someone calls locals()? What if the outer e was nonlocal or global? What if either e is referenced by an inner function? What if another statement re-rebinds e inside the first statement? What if you do this inside a class (or at top level)?I think for a quick hack to play with this, you don't have to worry about any of those issues; just say that's illegal, and whatever happens (even a segfault) is your own fault for trying it. (And obviously the same if some C extension calls PyFrame_LocalsToFast or equivalent.) But for a real implementation, I'm not even sure what the rules should be, much less how to implement them. (I'm guessing the implementation could either involve having a stack of symbol tables, or tagging things at the AST level while we've still got a tree and using that info in the last step, but I think there's still a problem telling the machinery how to set up closure cells to link inner functions' free variables.)

Also, all of this assumes that none of the machinery, even for tracebacks and debugging, cares about the name of the variable, just its index. Is that true?

It might be better to not start off worrying about how to get there from here, and instead first try to design the complete scoping rules for a language that's like Python but with nested scopes, and then identify all the places that it would differ from Python, and then decide which parts of the existing machinery you can hack up and which parts you have to completely replace. (Maybe, for example, would be easier with new bytecodes to replace LOAD_CLOSURE, LOAD_DEREF, MAKE_CLOSURE, etc. than trying to modify the data to make those bytecodes work properly.)

> Example:
> def f(x):
>    e = 2.718281828
>    try:
>        return e/x
>    except ZeroDivisionError as e:
>        raise ContrivedCodeException from e
> Currently, f.__code__.co_varnames is ('x', 'e'), and all the
> references to e are working with slot 1; imagine if, instead,
> co_varnames were ('x', 'e', 'e') and the last two lines used slot 2
> instead. Then the final act of the except clause would be to unbind
> its local name e (slot 2),
> and then any code after the except block
> would use slot 1 for e, and the original value would "reappear".

I don't think that "unbind" is a real step that needs to happen. The names have to get mapped to slot numbers at compile time anyway, so if all code outside of the except clause was compiled to LOAD_FAST 1 instead of LOAD_FAST 2, it doesn't matter that slot 2 has the same name. The only thing you need to do is the existing implicit "del e" on slot 2. (If you somehow managed to do another LOAD_FAST 2 after that, it would just be an UnboundLocalError, which is fine. But no code outside the except clause can compile to that anyway, unless there's a bug in your idea of its implementation or someone does some byteplay stuff).

> The only place that would need to "know" about the stack of scopes is
> the compilation step; everything after that just uses the slots. Is
> this feasible?
> ChrisA
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/

More information about the Python-ideas mailing list