[Tim] >> It's been noted several times recently that the example PEP 572 gives >> as _not_ working: >> >> total = 0 >> progressive_sums = [total := total + value for value in data] >> >> was the original use case that prompted work on the PEP. You gotta >> admit that's ironic ;-)
[Nick] > After pondering this case further, I think it's also worth noting that that > *particular* example could also be addressed by: > > 1. Allowing augmented assignment *expressions* > 2. Changing the scoping rules for augmented assignment operations in general > such that they *don't change the scope of the referenced name* > > Writing "i += n" without first declaring the scope of "i" with "i = 0", > "nonlocal i" or "global i" is one of the most common sources of > UnboundLocalError after all, so I'd be surprised to find anyone that > considered the current augmented assignment scoping rules to be outside the > realm of reconsideration. Yes, it's worth considering. In my experience, I don't believe I've ever had an UnboundLocalError for which a correct fix was to add `nonlocal`. Usually the correct fix was to add `global`, but that's mostly due to an old habit of using piles of globals to count trips through various code paths, used by a watchdog thread that periodically wakes up to display (the global) counts. > The accumulation example would then be written: > > total = 0 > progressive_sums = [total += value for value in data] > if progressive_sums: > assert total == progressive_sums[-1] And the divide-out-small-primes example could be factor = -42 while any(n % (factor += p - factor) == 0 for p in small_primes): n //= factor Heh ;-) > 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 ;-) 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 ;-) 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. OTOH, I don't (yet?) see a way the change could break code that currently works, so it remains worth thinking about. 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! _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/