[Python-ideas] Inline assignments using "given" clauses

Nick Coghlan ncoghlan at gmail.com
Thu May 17 09:20:33 EDT 2018

On 15 May 2018 at 01:53, Tim Peters <tim.peters at gmail.com> wrote:

> [Nick]
> > The question would then turn to "What if you just want to bind the target
> > name, without considering the old value?". And then *that's* where "NAME
> : =
> > EXPR" would come in: as an augmented assignment operator that used
> augmented
> > assignment scoping semantics, rather than regular local name binding
> > semantics.
> Plain old ":=" would somehow be viewed as being an augmented
> assignment operator too? ... OK, the meaning is that augmented
> assignment _and_ ":=" would resolve the target's scope in the way the
> containing block resolves it.
> > That would mean *directly* overturning PEP 3099's rejection of the idea
> of
> > using "NAME := EXPR" to imply "nonlocal NAME" at function scope, but
> that's
> > effectively on the table for implicit functions anyway (and I'd prefer to
> > have ":=" be consistent everywhere, rather than having to special case
> the
> > implicit scopes).
> Creating a local in a containing scope by magic is never done by
> Python today.  Extending that beyond "need" seems potentially
> perilous.  For example, it can already be tedious to figure out which
> names _are_ local to a function by staring at the function's code, but
> people quickly get better at that over time; change the rules so that
> they _also_ have to stare at all immediately contained  functions too
> to figure it out, and it may become significantly harder (OK, I didn't
> declare `x`, and a contained function did `x := 3.14` but `x` isn't
> declared there either - I guess it's my `x` now).  Then again, if
> they're doing that much function nesting they deserve whatever they
> get ;-)

More likely they'd get a compile time error complaining that the compiler
couldn't figure out what they meant, and asking them to be clearer about
the intended scoping.

Restrict it to that only synthetically generated functions can pull
> off this trick by magic (where there are real use cases to motivate
> it), and they still don't have to look outside the body of a
> function's text to figure it out.  Visually, there's no distinction
> between the code running in the function's scope and in scopes
> synthesized to implement comprehensions appearing in the function's
> text.  The comprehensions aren't even indented more.
> So, offhand, I'm not sure that the right way to address something you
> view as a wart is to vastly expand its reach to 12 operators that
> impose it on everyone everywhere every time they're used ;-)

Once I reframed the idea as being like an augmented assignment, your
proposed semantics seemed a lot less magical to me, since I was able to
define them in terms of "find the assignment or declaration that already
exists", rather than implicitly creating a new one. If the compiler can't
find a suitable target scope, then it can throw AmbiguousTargetError (which
would be an improvement over the runtime UnboundLocalError you typically
get today).

> Seriously, I do suspect that in
>     def f(...):
>         ... no instances of `s` ...
>         s += f"START {time.time():.2f}"
> it's overwhelmingly more likely that they simply forgot to do
>         s = ""
> earlier in `f` than they actually wanted to append to whatever `s`
> means in f's parent block..  That's a radical change to what people
> have come to expect `NAME +=` to do.

I think this is the key argument in favour of only allowing the "implicitly
nonlocal rebinding" behaviour in lambda expressions, generator expressions,
and comprehensions, as that way the search for a target to bind would
always terminate at the containing block (just as it does today).

BTW, would
>     def f():
>         x := 3.14
>         x = 3.14
> be a compile-time error?  Everyone agreed the analogous case would be
> in synthetic functions.  Fine by me!

Yeah, I think that would be an AmbiguousTargetError, as when the compiler
saw "x := 3.14", it wouldn't have seen "x = 3.14" yet.

For other augmented assignments, it would be a DeprecationWarning for the
time being, and become an AmbiguousTargetError at a later date.

(This also relates to the previous point: if "x := 3.14" can be implicitly
nonlocal, then I couldn't answer that question without knowing which names
were defined in outer scopes. By contrast, if the implicit access to outer
scopes is limited to inline scopes accessing their containing scope, then
this example becomes precisely analagous to the current syntax error for
binding a name as a local before declaring it as global or nonlocal. The
statement of ambiguity would arise from the fact that when we see "TARGET
:= EXPR" at statement level, we don't know if the missing prior statement
is a local variable assignment, a type declaration, or a global or nonlocal


Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180517/d7140cb3/attachment-0001.html>

More information about the Python-ideas mailing list