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

Tim Peters tim.peters at gmail.com
Sat Apr 28 20:30:10 EDT 2018


[Tim Delaney <timothy.c.delaney at gmail.com>]
> My big concern here involves the:
>
> if local(m = re.match(regexp, line)):
>     print(m.group(0))
>
> example. The entire block needs to be implicitly local for that to work -
> what happens if I assign a new name in that block?

I really don't know what you're asking there.  Can you make it
concrete?  If, e.g., you're asking what happens if this appeared after
the `print`:

        x = 3.14

then the answer is "the same as what would happen if `local` had not
been used".  We can't know what that is without context, though.
Maybe x is global.  Maybe x was declared nonlocal earlier.  Maybe it's
function-local.  While it may be irrelevant to what you're asking, I
noted just before:

"""
Time to note another subtlety:  people don't _really_ want "a new
scope" in Python.  If they did, then _every_ name appearing in a
binding context (assignment statement target, `for` target, ...) for
the duration would vanish when the new scope ended.  What they really
want is a new scope with an implied "nonlocal" declaration for every
name appearing in a binding context _except_ for the specific names
they're effectively trying to declare as being "sublocal" instead.
"""

If by "new name" you mean that `x` didn't appear in any earlier line,
then Python's current analysis would classify `x` as local to the
current function (or as global if this is module-level code ...).
That wouldn't change.


> Also, what happens with:
>
> if local(m = re.match(regexp1, line)) or local(m = re.match(regexp2, line)):
>     print(m.group(0))

As is, if `local()` appears in an `if` or `while` expression, the
scope extends to the end of the block construct.  In that specific
case, I'd expect to get a compile-time error, for attempting to
initialize the same name more than once in the new scope.

If the scope also encompasses associated `elif` statements, I'd
instead expect local(m=...) in one of those to be treated as a
re-initialization ;-) instead.

> Would the special-casing of local still apply to the block?

As is, `local()` in an `if` or `while` expressions triggers deeply
magical behavior, period.

> Or would you need to do:
>
> if local(m = re.match(regexp1, line) or re.match(regexp2, line)):
>     print(m.group(0))

Yes, not to trigger magical behavior, but to avoid the compile-time error.


> This might just be lack of coffee and sleep talking, but maybe new "scoping
> delimiters" could be introduced. Yes - I'm suggesting introducing curly
> braces for blocks, but with a limited scope (pun intended).  Within a local
> {} block statements and expressions are evaluated exactly like they
> currently are, including branching statements, optional semi-colons, etc.
> The value returned from the block is from an explicit return, or the last
> evalauted expression.

>> a = 1
>> b = 2
>> c = local(a=3) * local(b=4)

> c = local { a=3 } * local { b=4 }

>> c = local(a=3 , b=4, a*b)

> c =  local
> {
>     a = 3
>     b = 4
>     a * b
> }

>> c = local(a=3, b=local(a=2, a*a), a*b)

> c =
> local
> {
>     a = 3
>     b = local(a=2, a*a)

I expect you wanted

    b = local{a=2; a*a}

there instead (braces instead of parens, and semicolon instead of comma).

>     return a * b
> }

>> r1, r2 = local(D = b**2 - 4*a*c,
>>                sqrtD = math.sqrt(D),
>>                twoa = 2*a,
>>                ((-b + sqrtD)/twoa, (-b - sqrtD)/twoa))

> r1, r2 = local {
>     D = b**2 - 4*a*c
>     sqrtD = math.sqrt(D)
>     twoa = 2*a
>     return ((-b + sqrtD)/twoa, (-b - sqrtD)/twoa)
> }

>> if local(m = re.match(regexp, line)):
>>     print(m.group(0))

> if local { m = re.match(regexp, line) }:
>     print(m.group(0))

OK, this is the only case in which you used it in an `if` or `while`
expression.  All the questions you asked of me at the start can be
asked of this spelling too.  You seemed to imply at the start that the
right curly brace would always mark the end of the new scope.  But if
that's so, the `m` in `m.group()` has nothing to do with the `m`
assigned to in the `local` block - _that_ scope ended before `print`
was reached.

So if you're not just trying to increase the level of complexity of
what can appear in a local block, a fundamental problem still needs
solving ;-)  I suppose you could solve it like so:

local { m = re.match(regexp, line)
           if m:
               print(m.group(0))
         }

but, besides losing the "shortcut", it would also mean something
radically different if

               x = 3.14

appeared after the "print".  Right?  If a "local block" is taken
seriously, then _all_ names bound inside it vanish when the block
ends.


> And a further implication:
>
> a = lambda a, b: local(c=4, a*b*c)
>
> a = lambda a, b: local {
>     c = 4
>     return a * b * c
> }

If people do want a for-real "new scope" in Python, I certainly agree
`local {...}` is far better suited for that purpose.


More information about the Python-ideas mailing list