On Monday 27 October 2003 04:58 am, Guido van Rossum wrote:
Ideally, augmented assignments would also become "rebinding". However, this may have compatibility problems.
Unfortunately yes. It might have been better to define them that way in the first place, but changing them now is dubious.
I'm not so sure. You need an existing binding before an augmented assignment will work, so I don't think there can be any correct existing usages that would be broken by this.
Indeed. If x is neither local not declared global, x+=... is always an error, even if an x at an intermediate level exists, so THAT shouldn't be used as an argument against this.
Actually, if the compiler were able to diagnose that, it would be wonderful -- but I don't think it can, because it can make no assumptions regarding what might be defined in global scope (or at least it definitely can't make any such assumptions now).
So, yes, any sensible program that works today would keep working. I dunno about NON-sensible programs such as:
def outer(): x = 23 def inner(): exec 'x = 45' x+=1 # etc etc
but then I guess the presence of 'exec' might be defined to change semantics of += and/or disallow := or whatever else, just as today it turns off local-variable optimizations.
My slight preference for leaving += and friends alone is that a function using them to rebind nonlocals would be hard to read, that since the change only applies when the LHS is a bare name the important use cases for augmented assignment don't apply any way, that it's a bit subtle to explain that foo.bar += baz ( += on a dotted name) implies a plain assignment (setattr) on foo.bar while foo_bar += baz ( += on bare name) might imply a := assignment (rebinding a nonlocal) IF there are no "foo_bar = baz" elsewhere in the same function BUT it would imply a plain assignment if there ARE other plain assignments to the same name in the same function, ...
IOW it seems to me that we're getting into substantial amounts of subtlety in explaining (and thus maybe in implementing) a functionality change that's not terribly useful anyway and may damage rather than improve readability when it's used.
Taking the typical P. Graham accumulator example, say:
with += rebinding, we can code this:
def accumulator(n=0): def increment(i): n += i return n return increment
but without it, we would code:
def accumulator(n=0): def increment(i): n := n + i return n return increment
and it doesn't seem to me that the two extra keystrokes are to be considered a substantial price to pay. Admittedly in such a tiny example readability is just as good either way, as it's obvious which n we're talking about (there being just one, and extremely nearby wrt the point of use of either += or := ).
Suppose we wanted to have the accumulator "saturate" -- if the last value it returned was > m it must restart accumulating from zero. Now, without augmented assignment:
def accumulator_saturating(n=0, m=100): def increment(i): if n > m: n := i else: n := n + i return n return increment
we have a pleasing symmetry and no risk of errors -- if we mistakenly use an = instead of := in either branch the compiler will be able to let us know immediately. (Actually I'd be quite tempted to code the if branch as "n := 0 + i" to underscore the symmetry, but maybe I'm just weird:-).
If we do rely on augmented assignment being "rebinding":
def accumulator_saturating(n=0, m=100): def increment(i): if n > m: n = i else: n += i return n return increment
the error becomes a runtime rather than compile-time one, and does take a (small but non-zero) time to discover it. The += 's subtle new semantics (rebinds either a local or nonlocal, depending on how other assignments elsewhere in the function are coded) do make it slightly harder to understand and explain, compared to my favourite approach, which is: := is the ONLY way to rebind a nonlocal name (and only ever does that, only with a bare name on LHS, etc, etc) which can't be beaten in terms of how simple it is to understand and explain. The compiler could then diagnose an error when it sees := and += used on the same barename in the same function (and perhaps give a clear error message suggesting non-augmented := usage in lieu of the augmented assignment).
Can somebody please show a compelling use case for some "nonlocal += expr" over "nonlocal := nonlocal + expr" , sufficient to override all the "simplicity" arguments above? I guess there must be some, since popular feeling appears to be in favour of having augmented-assignment as "rebinding", but I can't see them.