Why does += trigger UnboundLocalError?

We all know that the following code won't work because of UnboundLocalError and that to get around it, one needs to use nonlocal:
def accum(): ... x = 0 ... def inner(): ... x += 1 ... return x ... return inner ... inc = accum() inc() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in inner UnboundLocalError: local variable 'x' referenced before assignment
But why does this happen? Let's think about this a little more closely: += is not the same as =. A += can only happen if the left-hand term was already defined. So, why does the compiler treat this as though there were an assignment inside the function? Compare:
def accum(): ... x = [] ... def inner(): ... x.append(1) ... return x ... return inner ... inc = accum() inc() [1] inc() [1, 1] inc() [1, 1, 1]
So, if I changed += to .append, the code suddenly works fine. Heck, I could also change it to x.__iadd__ if x happens to have that attribute. As we all know, adding an = anywhere to the function bound will cause x to be considered a local. So, for example, we can make the .append example fail by adding some unreachable code:
def accum(): ... x = [] ... def inner(): ... x.append(1) ... return x ... x = 0 #Won't ever be reached, but will cause x to be considered a local ... return inner ... inc = accum() inc() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in inner UnboundLocalError: local variable 'x' referenced before assignment
So, my proposal is that += by itself should not cause x to be considered a local variable. There should need to be a normal = assignment for the compiler to count x as a local. If the objection to my proposal is that I'm being "implicit and not explicit" because it would be like there's an implicit "nonlocal," my rebuttal is that we already have "implicit" nonlocals in the case of .append. -- Carl Johnson

On 01.06.2011 06:52, Carl M. Johnson wrote:
We all know that the following code won't work because of UnboundLocalError and that to get around it, one needs to use nonlocal:
def accum(): ... x = 0 ... def inner(): ... x += 1 ... return x ... return inner ... inc = accum() inc() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in inner UnboundLocalError: local variable 'x' referenced before assignment
But why does this happen? Let's think about this a little more closely: += is not the same as =. A += can only happen if the left-hand term was already defined. So, why does the compiler treat this as though there were an assignment inside the function?
Because x += y is equivalent to x = x.__iadd__(y) and therefore an assignment is going on here. Therefore, it's only logical to treat it as such when determining scopes. Georg

On Tue, May 31, 2011 at 7:48 PM, Georg Brandl <g.brandl@gmx.net> wrote:
Because x += y is equivalent to
x = x.__iadd__(y)
and therefore an assignment is going on here. Therefore, it's only logical to treat it as such when determining scopes.
But the difference is that you can only use += if the LHS name already exists and is defined. So, it couldn't possibly be referring to a local name if it's the only assignment-like statement within a function body. How could it refer to a local if it has to refer to something that already exists?

On 01.06.2011 08:48, Carl M. Johnson wrote:
On Tue, May 31, 2011 at 7:48 PM, Georg Brandl <g.brandl@gmx.net <mailto:g.brandl@gmx.net>> wrote:
Because x += y is equivalent to
x = x.__iadd__(y)
and therefore an assignment is going on here. Therefore, it's only logical to treat it as such when determining scopes.
But the difference is that you can only use += if the LHS name already exists and is defined. So, it couldn't possibly be referring to a local name if it's the only assignment-like statement within a function body. How could it refer to a local if it has to refer to something that already exists?
Sure, this can only work if the local is assigned somewhere before the augmented assign statement. But this is just like accessing a local before its assignment: in the case of x = 1 def f(): print x x = 2 we also don't treat the first "x" reference as a nonlocal. And the fact remains that augassign *is* an assignment, and the rule is that assignments to out-of-scope names are only allowed when declared using "global" or "nonlocal". Georg

On Tue, May 31, 2011 at 9:05 PM, Georg Brandl <g.brandl@gmx.net> wrote:
Sure, this can only work if the local is assigned somewhere before the augmented assign statement. But this is just like accessing a local before its assignment: in the case of
x = 1 def f(): print x x = 2
we also don't treat the first "x" reference as a nonlocal.
I don't think that's a counterexample to the point I'm trying to make. We all agree that if there's an x= somewhere in the function body, then we have to treat the variable as a local. The only possible way around that would be to solve the halting problem in order to figure out if a particular line of code will be reached or not. Agreed, sure, we have to treat the LHS of = as a local. But += is fundamentally different. You cannot have a += statement unless somewhere out there there is a matching = statement. It cannot exist independently. It never works on its own. So, if there is a += statement in the function body and there isn't an = statement in the function body it cannot work. Ever. All function bodies that have a += but no corresponding = or nonlocal are, as of today, broken code. So, if we were to change Python to make += not cause a variable to become a local, it wouldn't change how any (working) Python code today functions (it might causes some tests to change if they were counting on the error). This would be a completely backwards compatible change. Or am I missing something? Is there any scenario where you can get away with using += without = or nonlocal? I guess you could do something with locals().update or the stackframe, but my understanding is that those hacks don't count for language purposes. -- Carl

On 1 June 2011 09:26, Carl M. Johnson <cmjohnson.mailinglist@gmail.com> wrote:
I don't think that's a counterexample to the point I'm trying to make. We all agree that if there's an x= somewhere in the function body, then we have to treat the variable as a local. The only possible way around that would be to solve the halting problem in order to figure out if a particular line of code will be reached or not. Agreed, sure, we have to treat the LHS of = as a local. But += is fundamentally different. You cannot have a += statement unless somewhere out there there is a matching = statement. It cannot exist independently. It never works on its own. So, if there is a += statement in the function body and there isn't an = statement in the function body it cannot work. Ever. All function bodies that have a += but no corresponding = or nonlocal are, as of today, broken code. So, if we were to change Python to make += not cause a variable to become a local, it wouldn't change how any (working) Python code today functions (it might causes some tests to change if they were counting on the error). This would be a completely backwards compatible change. Or am I missing something? Is there any scenario where you can get away with using += without = or nonlocal? I guess you could do something with locals().update or the stackframe, but my understanding is that those hacks don't count for language purposes.
The place to start here is section 4.1 of the language reference (Naming and Binding). Specifically, "A scope defines the visibility of a name within a block. If a local variable is defined in a block, its scope includes that block." Your modification of augmented assignment implies that a block can contain 2 different scopes - consider x = 1 def f(): # The next statement uses the global x x += 1 x = 2 # From here, you have a local x That fundamentally changes the language semantics. If you want to push this change, I'd suggest you start by proposing a change to the language reference section I mentioned above to define your proposed new scoping rules. In my view, that would be sufficiently hard that it'd kill this proposal, but if you can manage to do it, then you may have a chance to get your change accepted. Paul.

I think you missed this statement, even though you quoted it. On 2011-06-01 10:51, Paul Moore wrote:
On 1 June 2011 09:26, Carl M. Johnson <cmjohnson.mailinglist@gmail.com> wrote:
We all agree that if there's an x= somewhere in the function body, then we have to treat the variable as a local.
This means that your example:
x = 1 def f(): # The next statement uses the global x x += 1 x = 2 # From here, you have a local x
Would behave exactly as it does today under the proposed new semantics. Specifically, the "x = 2" statement (and the lack of a nonlocal statement) forces x to be local throughout the function, and the "x += 1" statement then tries to read the local "x" and fails.
That fundamentally changes the language semantics.
I don't think it does. It only makes a difference for functions that contains an augmented assignment to a name without also containing a regular assignment to that name. This case will change from being an error to doing something well-defined and useful. FWIW, I'm +1 on the idea. Best regards - Jacob

On Tue, May 31, 2011 at 11:09 PM, Jacob Holm <jh@improva.dk> wrote:
x = 1
def f(): # The next statement uses the global x x += 1 x = 2 # From here, you have a local x
Specifically, the "x = 2" statement (and the lack of a nonlocal statement) forces x to be local throughout the function, and the "x += 1" statement then tries to read the local "x" and fails.
Yes, Jacob has got exactly what I was proposing. x += 1; x = 2 should continue to fail, since there would be a = statement in the function body in that case. -- Carl

Yes, Jacob has got exactly what I was proposing. x += 1; x = 2 should continue to fail, since there would be a = statement in the function body in that case.
-- Carl
My first reaction was: +1 on the proposed change. It seemed logical. Then I had a reservation: it would widen the semantic difference between x += 1 and x = x + 1 which could trip someone innocently making a "trivial" code change from the former to the latter (x unintentionally becomes a local). So how about going further and say that x is only interpreted as local if there is at least one NON-augmented assignment in which x appears as a target on the LHS but x does NOT appear on the RHS? I.e. x = x + 1 (like "x += 1") does not (by itself) make x local. Or is this getting too hard to explain? Best wishes Rob Cliffe

On Wed, Jun 1, 2011 at 12:43 PM, Rob Cliffe <rob.cliffe@btinternet.com> wrote:
My first reaction was: +1 on the proposed change. It seemed logical.
Then I had a reservation: it would widen the semantic difference between x += 1 and x = x + 1 which could trip someone innocently making a "trivial" code change from the former to the latter (x unintentionally becomes a local).
So how about going further and say that x is only interpreted as local if there is at least one NON-augmented assignment in which x appears as a target on the LHS but x does NOT appear on the RHS? I.e. x = x + 1 (like "x += 1") does not (by itself) make x local.
Or is this getting too hard to explain?
I think so; it also has the same disadvantage you mention of getting a semantic change from seemingly neutral changes, but for other changes. For example x = 1 if x == 0 else x-1 would keep x global, but changing it to: if x == 0: x = 1 else: x = x-1 would not do so. -- André Engels, andreengels@gmail.com

Carl M. Johnson wrote:
Agreed, sure, we have to treat the LHS of = as a local. But += is fundamentally different.
No it's not. It is fundamentally the same. Augmented assignment in Python *is* assignment, equivalent to x = x.__iadd__(other). That alone should be enough to kill this proposal stone dead. += is not, except by accident, an in-place addition operator. It is always a re-binding. (Mutable objects are free to mutate in place, if they choose, but the re-binding still takes place.)
You cannot have a += statement unless somewhere out there there is a matching = statement. It cannot exist independently. It never works on its own.
Neither does *any* attempt to access an unbound local. Python doesn't, and shouldn't, try to guess what you actually intended so as to make it work. If you want x to refer to a nonlocal, or a global, declare it as such. print x; x = 1 will fail unless there is an earlier x = something. x = x+1 will fail unless there is an earlier x = something. x += 1 will fail unless there is an earlier x = something. Why single out x += 1 for changed semantics to the rule that any assignment makes x a local? What if you don't have a non-local x, should Python guess that you wanted a global? Currently, the rule is simple: any assignment tells the compiler to treat x as local. If you want nonlocal or global, you have to declare it as such. Nice and simple. What actual real-world problem are you trying to solve that you want to change this behaviour? -1 on this change. -- Steven

Steven D'Aprano wrote:
Carl M. Johnson wrote:
Agreed, sure, we have to treat the LHS of = as a local. But += is fundamentally different.
No it's not. It is fundamentally the same. Augmented assignment in Python *is* assignment, equivalent to x = x.__iadd__(other).
Or x = x.__add__(other) if no __iadd__ exists for the object. ~Ethan~

On Wed, Jun 1, 2011 at 1:21 PM, Steven D'Aprano <steve@pearwood.info> wrote:
Currently, the rule is simple: any assignment tells the compiler to treat x as local. If you want nonlocal or global, you have to declare it as such. Nice and simple. What actual real-world problem are you trying to solve that you want to change this behaviour?
The best counter-arguments I've heard so far are Nick's (it would be a pain to go into the guts and change this, and you also need to think about PyPy, Jython, IronPy, etc., etc.) and this one. In terms of "real world problems" this solves, it makes the solution to the Paul Graham language challenge problem (build a function that returns an accumulator) one line shorter. Which is a bit silly, but so far as I can tell, nonlocal was created just to say we have an answer to the Paul Graham question. ;-) I think the benefit of saving that one line is probably outweighed by the brittleness that this would create (ie. changing x += 1 to x = x + 1 could break code), so I withdraw the proposal, at least for now. One additional problem that I ran into is this:
def f(): ... nonlocal count ... return count ... SyntaxError: no binding for nonlocal 'count' found
Nonlocal fails at the compilation stage if the variable isn't found. On the other hand, attribute lookup is delayed until runtime, so if by accident you did def f(): count = 0 def g(): cont += 1 #oops typo. return cont return g it's not clear when the function should fail: compile time or runtime. -- Carl

On Thu, Jun 2, 2011 at 3:17 PM, Carl M. Johnson <cmjohnson.mailinglist@gmail.com> wrote:
On Wed, Jun 1, 2011 at 1:21 PM, Steven D'Aprano <steve@pearwood.info> wrote:
Currently, the rule is simple: any assignment tells the compiler to treat x as local. If you want nonlocal or global, you have to declare it as such. Nice and simple. What actual real-world problem are you trying to solve that you want to change this behaviour?
The best counter-arguments I've heard so far are Nick's (it would be a pain to go into the guts and change this, and you also need to think about PyPy, Jython, IronPy, etc., etc.) and this one. In terms of "real world problems" this solves, it makes the solution to the Paul Graham language challenge problem (build a function that returns an accumulator) one line shorter. Which is a bit silly, but so far as I can tell, nonlocal was created just to say we have an answer to the Paul Graham question. ;-)
Nah, nonlocal was added because the introduction of decorators increased the use of closures, and boxing and unboxing variables manually is a PITA. Note that the "translation" of 'x += y' to 'x = x + y' is and always has been a gross oversimplification (albeit a useful one). Reality is complicated by possible provision of __iadd__ by the assignment target, as well as the need to pair up __getitem__/__setitem__ and __getattr__/__setattr__ appropriately when the target is a subscript operation or attribute access. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On 01.06.2011 10:26, Carl M. Johnson wrote:
On Tue, May 31, 2011 at 9:05 PM, Georg Brandl <g.brandl@gmx.net <mailto:g.brandl@gmx.net>> wrote:
Sure, this can only work if the local is assigned somewhere before the augmented assign statement. But this is just like accessing a local before its assignment: in the case of
x = 1 def f(): print x x = 2
we also don't treat the first "x" reference as a nonlocal.
I don't think that's a counterexample to the point I'm trying to make. We all agree that if there's an x= somewhere in the function body, then we have to treat the variable as a local. The only possible way around that would be to solve the halting problem in order to figure out if a particular line of code will be reached or not. Agreed, sure, we have to treat the LHS of = as a local. But += is fundamentally different.
You keep saying that, but I just can't see how += is fundamentally different from =, given its definition as x = x.__iadd__(y). This is a situation that comes up from time to time, where it seems logical to make a change that satisfies "DWIM" feelings, but makes the languge more inconsistent by introducing special cases. This doesn't feel right to me (and the Zen agrees ;) Georg

Carl M. Johnson wrote:
On Tue, May 31, 2011 at 7:48 PM, Georg Brandl wrote:
Because x += y is equivalent to
x = x.__iadd__(y)
and therefore an assignment is going on here. Therefore, it's only logical to treat it as such when determining scopes.
But the difference is that you can only use += if the LHS name already exists and is defined. So, it couldn't possibly be referring to a local name if it's the only assignment-like statement within a function body. How could it refer to a local if it has to refer to something that already exists?
Two problems. Firstly, what error should be raised here? --> def accum(): ... x = 0 ... def inner(): ... x1 += 1 ... return x ... return inner Secondly, the += operator may or may not be a mutating operator depending on the object it's used on: if the object does not have a __iadd__ method, it's not mutating; even if it does have an __iadd__ method, it may not be mutating -- it's up to the object to decide. --> class ex_int(int): ... def __iadd__(self, other): ... return self + other ... --> x = ex_int(7) --> x.__iadd__(3) 10 --> x 7 --> x = [1, 2, 3] --> x.__iadd__([4]) [1, 2, 3, 4] --> x [1, 2, 3, 4] -1 on changing the semantics. ~Ethan~

On Wed, Jun 01, 2011 at 07:48:19AM +0200, Georg Brandl wrote:
Because x += y is equivalent to
x = x.__iadd__(y)
and therefore an assignment is going on here. Therefore, it's only logical to treat it as such when determining scopes.
Georg
And it's like this so that immutable classes work correctly, as far as I can see. So one way to answer the original idea is: because of immutable classes, += does not have the same semantics as .append() Andrew

On Wed, 1 Jun 2011, Georg Brandl wrote:
On 01.06.2011 06:52, Carl M. Johnson wrote:
We all know that the following code won't work because of UnboundLocalError and that to get around it, one needs to use nonlocal:
def accum(): ... x = 0 ... def inner(): ... x += 1 ... return x ... return inner ... inc = accum() inc() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in inner UnboundLocalError: local variable 'x' referenced before assignment
But why does this happen? Let's think about this a little more closely: += is not the same as =. A += can only happen if the left-hand term was already defined. So, why does the compiler treat this as though there were an assignment inside the function?
Because x += y is equivalent to
x = x.__iadd__(y)
and therefore an assignment is going on here. Therefore, it's only logical to treat it as such when determining scopes.
Off on a bit of a tangent here - this behaviour always bugged me: --> x = ([],) --> x[0] += ['a'] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment --> x (['a'],) --> I understand by the definition why this happens as it does, but intuitively, I'd expect an operation to either fail and raise, or succeed and not. I see two possible ways to make this behave: we can look before we leap, and raise the exception before calling __iadd__, if the assigment would fail; or, we can change the definition to only perform the assignment if __iadd__ returns something other than self. Both these are, to some extent, incompatible language changes. Both change how I think about the original proposal: with the first option, it softens the argument about __iadd__ being called before the assignment, so strengthens the case for the status quo; with the second option, the definition of __iadd__ gets more complicated, making me less inclined to dive into this definition to explain the locality of the assigned variable, preferring it to be defined separately, and simply. Back from the tangent, I think Carl's proposal would make Python more difficult to understand rather than less, so -1 from me. /Paul

On Wed, Jun 1, 2011 at 2:52 PM, Carl M. Johnson <cmjohnson.mailinglist@gmail.com> wrote:
We all know that the following code won't work because of UnboundLocalError and that to get around it, one needs to use nonlocal:
There's no fundamental reason this couldn't change, but actually changing it simply isn't worth the hassle, so the status quo wins the stalemate. I elaborated further on this point when the topic came up last year: http://mail.python.org/pipermail/python-ideas/2010-June/007448.html Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Nick Coghlan wrote:
On Wed, Jun 1, 2011 at 2:52 PM, Carl M. Johnson <cmjohnson.mailinglist@gmail.com> wrote:
We all know that the following code won't work because of UnboundLocalError and that to get around it, one needs to use nonlocal:
There's no fundamental reason this couldn't change, but actually changing it simply isn't worth the hassle, so the status quo wins the stalemate.
I elaborated further on this point when the topic came up last year: http://mail.python.org/pipermail/python-ideas/2010-June/007448.html
Maybe I get to learn something new about Python today. Several times in that thread it was stated that --> a += 1 is a shortcut for --> a.__iadd__(1) It seems to me that this is an implementation detail, and that the actual "longcut" is --> a = a + 1 Likewise, the shortcut of --> some_list[func_with_side_effects()] += some_value is the same as --> index = func_with_side_effects() --> some_list[index] = some_list[index] + some_value Is my understanding correct? ~Ethan~

On Wed, Jun 1, 2011 at 9:56 AM, Ethan Furman <ethan@stoneleaf.us> wrote:
Several times in that thread it was stated that
--> a += 1
is a shortcut for
--> a.__iadd__(1)
It seems to me that this is an implementation detail, and that the actual "longcut" is
--> a = a + 1
...
~Ethan~
a += 1 is not a shortcut for a.__iadd__(1). It's a shortcut for a = a.__iadd(1). Otherwise this wouldn't work:
x = (1,) x += (2,) x (1, 2)
Note the difference between these two is one opcode:
def f(x,y): x += y dis.dis(f) 2 0 LOAD_FAST 0 (x) 3 LOAD_FAST 1 (y) 6 *INPLACE_ADD* 7 STORE_FAST 0 (x) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE def g(x,y): x = x + y
dis.dis(g) 2 0 LOAD_FAST 0 (x) 3 LOAD_FAST 1 (y) 6 *BINARY_ADD* 7 STORE_FAST 0 (x) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE
--- Bruce Follow me: http://www.twitter.com/Vroo http://www.vroospeak.com Latest tweet: SO disappointed end of the world didn't happen AGAIN! #y2k<http://twitter.com/#!/search?q=%23y2k> #rapture <http://twitter.com/#!/search?q=%23rapture> Now waiting for 2038! #unixrapture <http://twitter.com/#!/search?q=%23unixrapture>

Bruce Leban wrote:
On Wed, Jun 1, 2011 at 9:56 AM, Ethan Furman wrote:
Several times in that thread it was stated that
--> a += 1
is a shortcut for
--> a.__iadd__(1)
It seems to me that this is an implementation detail, and that the actual "longcut" is
--> a = a + 1
...
~Ethan~
a += 1 is not a shortcut for a.__iadd__(1). It's a shortcut for a = a.__iadd(1). Otherwise this wouldn't work:
Right -- typo on my part, sorry.
Note the difference between these two is one opcode:
def f(x,y): x += y dis.dis(f) 2 0 LOAD_FAST 0 (x) 3 LOAD_FAST 1 (y) 6 *INPLACE_ADD* 7 STORE_FAST 0 (x) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE def g(x,y): x = x + y
dis.dis(g) 2 0 LOAD_FAST 0 (x) 3 LOAD_FAST 1 (y) 6 *BINARY_ADD* 7 STORE_FAST 0 (x) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE
Note also that INPLACE_ADD will call the the BINARY_ADD method if no __iadd__ method exists. ~Ethan~

On 6/1/2011 1:32 PM, Bruce Leban wrote:
def f(x,y): x += y dis.dis(f) 2 0 LOAD_FAST 0 (x) 3 LOAD_FAST 1 (y) 6 *INPLACE_ADD* 7 STORE_FAST 0 (x) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE def g(x,y): x = x + y
dis.dis(g) 2 0 LOAD_FAST 0 (x) 3 LOAD_FAST 1 (y) 6 *BINARY_ADD* 7 STORE_FAST 0 (x) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE
(In 3.2, one no longer needs to wrap code in a function to dis it. see below.) To see the 'calculate the source/target just once instead of twice' part, you need a source/target that actually requires calculation.
from dis import dis dis('x[i] = x[i] + 1') 1 0 LOAD_NAME 0 (x) 3 LOAD_NAME 1 (i) 6 BINARY_SUBSCR 7 LOAD_CONST 0 (1) 10 BINARY_ADD 11 LOAD_NAME 0 (x) 14 LOAD_NAME 1 (i) 17 STORE_SUBSCR 18 LOAD_CONST 1 (None) 21 RETURN_VALUE
dis('x[i] += 1') 1 0 LOAD_NAME 0 (x) 3 LOAD_NAME 1 (i) 6 DUP_TOP_TWO 7 BINARY_SUBSCR 8 LOAD_CONST 0 (1) 11 INPLACE_ADD 12 ROT_THREE 13 STORE_SUBSCR 14 LOAD_CONST 1 (None) 17 RETURN_VALUE
Even this does not show much difference as the dup and rotate substitute for two loads but do not actually save any calculation. However,
dis('a.b[c+d] = a.b[c+d] + 1') 1 0 LOAD_NAME 0 (a) 3 LOAD_ATTR 1 (b) 6 LOAD_NAME 2 (c) 9 LOAD_NAME 3 (d) 12 BINARY_ADD 13 BINARY_SUBSCR 14 LOAD_CONST 0 (1) 17 BINARY_ADD 18 LOAD_NAME 0 (a) 21 LOAD_ATTR 1 (b) 24 LOAD_NAME 2 (c) 27 LOAD_NAME 3 (d) 30 BINARY_ADD 31 STORE_SUBSCR 32 LOAD_CONST 1 (None) 35 RETURN_VALUE
dis('a.b[c+d] += 1') 1 0 LOAD_NAME 0 (a) 3 LOAD_ATTR 1 (b) 6 LOAD_NAME 2 (c) 9 LOAD_NAME 3 (d) 12 BINARY_ADD 13 DUP_TOP_TWO 14 BINARY_SUBSCR 15 LOAD_CONST 0 (1) 18 INPLACE_ADD 19 ROT_THREE 20 STORE_SUBSCR 21 LOAD_CONST 1 (None) 24 RETURN_VALUE
The latter has the same dup-rotate in place of a bit more calculation. The same would be true of, for instance, f(a).b. -- Terry Jan Reedy

On 6/1/2011 12:52 AM, Carl M. Johnson wrote:
So, my proposal is that += by itself should not cause x to be considered a local variable.
Right now, 'augmented assigment' is uniformly what it says: an assignment with augmented behavior. 'expr1 op= expr2' is *defined* as being the same as 'expr1 = expr1 op expr2' except that expr1 is evauluated just once*, and if expr1 evaluates to a mutable, the op can be done in place. Some consider the second exception to be a confusing complication and a mistake. Your proposal would require a rewrite of the definition and would add additional complication. Some would then want another exception for when expr1 evaluates to a mutable within an immutable (see Paul Svensson's post). While I do understand your point, I also value uniformity. -1 * It is actually more complicate than than. Expr1 is partially evaluated just once to an internal reference rather than to an object. That reference is then used once to fetch the existing object and once again to rebind to the new or mutated object. Still, it is one behavior for all occurrences. -- Terry Jan Reedy

On 6/1/2011 1:41 PM, Terry Reedy wrote:
On 6/1/2011 12:52 AM, Carl M. Johnson wrote:
So, my proposal is that += by itself should not cause x to be considered a local variable.
While I do understand your point, I also value uniformity. -1
There is another problem I had not thought of before. Right now, Python has (always had) a simple rule: code in a function CANNOT rebind names in outer scopes unless the function has a global or nonlocal declaration. This simple, uniform rule benefits not only the interpreter but human readers. It should not be broken. def f(): 'doc for f' def g(): 'docstring of g' <body of g> <body of f> If g is the only nested function and the body of g does not have a nonlocal declaration (which OUGHT to be at the top if present), then a reader or maintainer of f knows (without reading g in detail) that nothing other that <body of f> can rebind f's locals. -- Terry Jan Reedy
participants (13)
-
Andre Engels
-
andrew cooke
-
Bruce Leban
-
Carl M. Johnson
-
Ethan Furman
-
Georg Brandl
-
Jacob Holm
-
Nick Coghlan
-
Paul Moore
-
Paul Svensson
-
Rob Cliffe
-
Steven D'Aprano
-
Terry Reedy