[Python-ideas] A "local" pseudo-function

Soni L. fakedme+py at gmail.com
Sat Apr 28 10:21:09 EDT 2018



On 2018-04-27 11:37 PM, Tim Peters wrote:
> A brain dump, inspired by various use cases that came up during the
> binding expression discussions.
>
> Idea:  introduce a "local" pseudo-function to capture the idea of
> initialized names with limited scope.
>
> As an expression, it's
>
>      "local" "(" arguments ")"
>
> - Because it "looks like" a function call, nobody will expect the targets
>    of named arguments to be fancier than plain names.
>
> - `a=12` in the "argument" list will (& helpfully so) mean pretty much the
>    same as "a=12" in a "def" statement.
>
> - In a "local call" on its own, the scope of a named argument begins at the
>    start of the next (if any) argument, and ends at the closing ")".  For the
>    duration, any variable of the same name in an enclosing scope is shadowed.
>
> - The parentheses allow for extending over multiple lines without needing
>    to teach editors (etc) any new tricks (they already know how to format
>    function calls with arglists spilling over multiple lines).
>
> - The _value_ of a local "call" is the value of its last "argument".  In
>    part, this is a way to sneak in C's comma operator without adding cryptic
>    new line noise syntax.
>
> Time for an example.  First a useless one:
>
> a = 1
> b = 2
> c = local(a=3) * local(b=4)
>
> Then `c` is 12, but `a` is still 1 and `b` is still 2.  Same thing in the end:
>
> c = local(a=3, b=4, a*b)
>
> And just to be obscure, also the same:
>
> c = local(a=3, b=local(a=2, a*a), a*b)
>
> There the inner `a=2` temporarily shadows the outer `a=3` just long
> enough to compute `a*a` (4).
>
> This is one that little else really handled nicely:
>
> r1, r2 = local(D = b**2 - 4*a*c,
>                 sqrtD = math.sqrt(D),
>                 twoa = 2*a,
>                 ((-b + sqrtD)/twoa, (-b - sqrtD)/twoa))
>
> Everyone's favorite:
>
> if local(m = re.match(regexp, line)):
>      print(m.group(0))
>
> Here's where it's truly essential that the compiler know everything
> about "local", because in _that_ context it's required that the new
> scope extend through the end of the entire block construct (exactly
> what that means TBD - certainly through the end of the `if` block, but
> possibly also through the end of its associated (if any) `elif` and
> `else` blocks - and similarly for while/else constructs).
>
> Of course that example could also be written as:
>
> if local(m = re.match(regexp, line), m):
>      print(m.group(0))
>
> or more specifically:
>
> if local(m = re.match(regexp, line), m is not None):
>      print(m.group(0))
>
> or even:
>
> if local(m = re.match(regexp, line)) is not None:
>      print(m.group(0))
>
> A listcomp example, building the squares of integers from an iterable
> but only when the square is a multiple of 18:
>
> squares18 = [i2 for i in iterable if local(i2=i*i) % 18 == 0]
>
> That's a bit mind-bending, but becomes clear if you picture the
> kinda-equivalent nest:
>
>      for i in iterable:
>          if local(i2=i*i) % 18 == 0:
>              append i2 to the output list
>
> That should also make clear that if `iterable` or `i` had been named
> `i2` instead, no problem.  The `i2` created by `local()` is in a
> wholly enclosed scope.
>
> Drawbacks:  since this is just a brain dump, absolutely none ;-)
>
> Q:  Some of those would be clearer if it were the more Haskell-like
>
>      local(...) "in" expression
>
> A: Yup, but for some of the others needing to add "in m" would be
> annoyingly redundant noise.  Making an "in" clause optional doesn't
> really fly either, because then
>
>      local(a='z') in 'xyz'
>
> would be ambiguous.  Is it meant to return `'xyz'`, or evaluate `'z'
> in 'xyz'`?  And any connector other than "in" would make the loose
> resemblance to Haskell purely imaginary ;-)
>
> Q: Didn't you drone on about how assignment expressions with complex
> targets seemed essentially useless without also introducing a "comma
> operator" - and now you're sneaking the latter in but _still_ avoiding
> complex targets?!
>
> A. Yes, and yes :-)  The syntactic complexity of the fully general
> assignment statement is just too crushing to _sanely_ shoehorn into
> any "expression-like" context.
>
> Q:  What's the value of this?   local(a=7, local(a=a+1, a*2))
>
> A: 16.  Obviously.
>
> Q:  Wow - that _is_ obvious!  OK, what about this, where there is no
> `a` in any enclosing scope:  local(a)
>
> A: I think it should raise NameError, just like a function call would.
> There is no _intent_ here to allow merely declaring a local variable
> without supplying an initial value.
>
> Q: What about local(2, 4, 5)?
>
> A: It should return 5, and introduce no names.  I don't see a point to
> trying to outlaw stupidity ;-)  Then again, it would be consistent
> with the _intent_ to require that all but the last "argument" be of
> the `name=expression` form.
>
> Q: Isn't changing the meaning of scope depending on context waaaay magical?
>
> A: Yup!  But in a language with such a strong distinction between
> statements and expressions, without a bit of deep magic there's no
> single syntax I can dream up that could work well for both that didn't
> require _some_ deep magic.  The gimmick here is something I expect
> will be surprising the first time it's seen, less so the second, and
> then you're never confused about it again.
>
> Q: Are you trying to kill PEP 572?
>
> A: Nope!  But since this largely subsumes the functionality of binding
> expressions, I did want to put this out there before 572's fate is
> history.  Binding expressions are certainly easier to implement, and I
> like them just fine :-)
>
>
> Note:  the thing I'm most interested in isn't debates, but in whether
> this would be of real use in real code.
> _______________________________________________
> 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/

Why not non-lexical variables?

Basically, make this work (and print 3):

     def test():
         i = 3
         def test_inner():
             print(i)
         hide i
         i = 4
         test_inner() # 3
         print(i) # 4

`hide`, unlike `del`, only applies to the current scope, and only 
forward. it does what it says on the tin: makes the variable 
disappear/be hidden.

ofc, python doesn't support shadowing of variables, so this is kinda 
useless, but eh I thought it was a cool idea anyway :/

(See also this thread on the Lua mailing list: 
https://marc.info/?l=lua-l&m=152149915527486&w=2 )


More information about the Python-ideas mailing list