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

Tim Peters tim.peters at gmail.com
Sun Apr 29 02:57:21 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?

[Tim Peters]
>> 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. ...

[Tim D]
> That's exactly what I was asking, and as I understand what you're saying, we
> would have a local name m available in the indented block which went away
> when the block ended, but any names modified in the block are not local to
> the block. That seems likely to be a source of errors.

If you what you _want_ is a genuinely new scope, yes.  But no actual
use cases so far wanted that at all.

This is the kind of code about which there have been background
complaints "forever":

    m1 = regexp1.match(line)
    m2 = regexp2.match(iine)
    if m1 and m2:
        do all sorts of stuff with m1 and/or m2,
        including perhaps modifying local variables
        and/or global variables
        and/or nonlocal variables

The complaints are of two distinct kinds:

1. "I want to compute m1 and m2 _in_ the `if` test".

2. "I don't want these temp names (m1 and m2) accidentally
   conflicting with local names already in scope - if these names
   already exist, I want the temp names to shadow their
   current bindings until the `if` structure is done".

So,

    if local(m1=regexp1.match(line),
              m2 = regexp2.match(iine),
              m1 and m2):

intends to address both complaints via means embarrassingly obvious to
the most casual observer ;-)

This is, e.g., the same kind of name-specific "shadowing" magically
done by list and dict comprehensions now, and by generator
expressions.  For example,

    [i**2 for i in range(10)]

has no effect on whatever `i` meant before the listcomp was executed.


> To clarify my understanding, if the names 'x' and 'm' did not exist prior to
> the following code, what would x and m refer to after the block completed?
>
> if local(m = re.match(regexp, line)):
>     x = 1
>     m = 2

I hope the explanation above made that clear.  What's wanted is
exactly what the current

    m = re.match(regexp, line):
    if m:
        x =1
        m = 2

_would_ do if only there were a sane way to spell "save m's current
status before that all started and restore it after that all ends".

So they want `x == 1` after it's over, and `m` to raise NameError.


>>> 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.

> Yes - I think this is exactly the same issue as with your proposed syntax.

Wholly agreed :-)


>> 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.

> Indeed, and I don't have a proposal - just concerns it wold be very
> difficult to explain and understand exactly what would happen in the case of
> something like:
>
> if local(m = re.match(regexp, line)):
>     x = 1
>     m = 2

Only names appearing as targets _in_ the `local(...)` are affected in
any way.  The states of those names are captured, then those names are
bound to the values of the associated expressions in the `local(...)`,
and when the scope of the `local` construct ends (which _is_ hard to
explain!) those names' original states are restored.

So the effects on names are actually pretty easy to explain:  all and
only the names appearing inside the `local(...)` are affected.


> Regarding the syntax, I didn't want to really change your proposal, but just
> thought the functionality was different enough from the function call it
> appears to be that it probably merits different syntax.

Probably so!


More information about the Python-ideas mailing list