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

Yury Selivanov yselivanov.ml at gmail.com
Fri Apr 27 23:34:44 EDT 2018


Hi Tim,

This is interesting. Even "as is" I prefer this to PEP 572. Below are some
comments and a slightly different idea inspired by yours (sorry!)

On Fri, Apr 27, 2018 at 10:41 PM Tim Peters <tim.peters at gmail.com> wrote:
[..]
> 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.
[..]
> 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

It does look like a function call, although it has a slightly different
syntax. In regular calls we don't allow positional arguments to go after
keyword arguments.  Hence the compiler/parser will have to know what
'local(..)' is *regardless* of where it appears.

If you don't want to make 'local' a new keyword, we would need to make the
compiler/parser to trace the "local()" name to check if it was imported or
is otherwise "local". This would add some extra complexity to already
complex code.  Another problematic case is when one has a big file and
someone adds their own "def local()" function to it at some point, which
would break things.

Therefore, "local" should probably be a keyword. Perhaps added to Python
with a corresponding "from __future__" import.

The other way would be to depart from the function call syntax by dropping
the parens.  (And maybe rename "local" to "let" ;))  In this case, the
syntax will become less like a function call but still distinct enough.  We
will be able to unambiguously parse & compile it.  The cherry on top is
that we can make it work even without a "__future__" import!

When we implemented PEP 492 in Python 3.5 we did a little trick in
tokenizer to treat "async def" in a special way. Tokenizer would switch to
an "async" mode and yield ASYNC and AWAIT tokens instead of NAME tokens.
This resulted in async/await syntax available without a __future__ import,
while having full backwards compatibility.

We can do a similar trick for "local" / "let" syntax, allowing the
following:

   "let" NAME "=" expr ("," NAME = expr)* ["," expr]

* "if local(m = re.match(...), m):" becomes
    "if let m = re.match(...), m:"

* "c = local(a=3) * local(b=4)" becomes
   "c = let a=3, b=4, a*b" or "c = (let a=3, b=4, a*b)"

*      for i in iterable:
           if let i2=i*i, i2 % 18 == 0:
              append i2 to the output list

etc.

Note that I don't propose this new "let" or "local" to return their last
assignment. That should be done explicitly (as in your "local(..)" idea):
  `let a = 'spam', a`.  Potentially we could reuse our function return
annotation syntax, changing the last example to `let a = "spam" -> a` but I
think it makes the whole thing to look unnecessarily complex.

One obvious downside is that "=" would have a different precedence compared
to a regular assignment statement. But it already has a different precedent
in function calls, so maybe this isn't a big deal, considered that we'll
have a keyword before it.

I think that "let" was discussed a couple of times recently, but it's
really hard to find a definitive reason of why it was rejected (or was it?)
in the ocean of emails about assignment expressions.

Yury


More information about the Python-ideas mailing list