This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Unclear behavior of += operator
Type: behavior Stage: resolved
Components: Interpreter Core Versions: Python 3.9
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: eric.smith, mscholle, steven.daprano, veky
Priority: normal Keywords:

Created on 2022-02-02 15:06 by mscholle, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (7)
msg412365 - (view) Author: Marek Scholle (mscholle) Date: 2022-02-02 15:06
Hi, I ran into discussion about scoping in Python (visibility of outer variables in nested functions, global, nonlocal) which made me to create for other some showcases.

I realized there is a space for ambiguity which I extracted to this REPL:

----
>>> x = []
>>> def f(): x += [1]
...
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in f
UnboundLocalError: local variable 'x' referenced before assignment
>>> x = []
>>> def f(): x.append(1)
...
>>> f()
>>> x
[1]
----

The documentation says about `x += [1]` it is "translated" to `x.__iadd__([1])`. It would be interesting to know if Python actually documents that `x += [1]` will err with `UnboundLocalError`.

I think there is a natural argument that `x += <rhs>` should behave as an in-place version of `x = x + <rhs>` (where `UnboundLocalError` makes perfect sense), but diving into documentation it seems that `x += <rhs>` should be a syntax sugar for `x.__iadd__(rhs)` in which case `UnboundLocalError` should not happen and looks like some parser artifact.
msg412366 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2022-02-02 15:19
You say: "The documentation says ..." but don't tell us which documentation.

This documentation:

https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-statements


tells us that augmented assignment is assignment:

"An augmented assignment evaluates the target (which, unlike normal assignment statements, cannot be an unpacking) and the expression list, performs the binary operation specific to the type of assignment on the two operands, and assigns the result to the original target."

And also: "the assignment done by augmented assignment statements is handled the same way as normal assignments."
msg412367 - (view) Author: Marek Scholle (mscholle) Date: 2022-02-02 15:33
Thanks for pointing to reference https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-statements

Although I can agree it tries to point to similarity with `x = x + 1`, it says about how `x += [1]` is processed:

(1) evaluate the target (`x`)
(2) evaluate the expression list (`[1]`)
(3) call `+=`, which I understand dispatch `x.__iadd__([1])`

I see there is no space left for `UnboundLocalError`.

> The assignment done by augmented assignment statements is handled the same way as normal assignments.

This is not a technical claim. They are not the same, and to claim "they are handled the same way" is strictly speaking an empty statement. For which definition of "same way"?
msg412374 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2022-02-02 16:38
The "evaluate the target" part causes the UnboundLocalError, just as in:

>>> x=1
>>> def f():
...   x
...   x = x + 1
... 
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'x' referenced before assignment
msg412375 - (view) Author: Marek Scholle (mscholle) Date: 2022-02-02 16:49
I don't understand the comment https://bugs.python.org/issue46612#msg412374

----
>>> def f(): x
...
>>> f()
----
is OK, so x is something which can be evaluated inside nested function, it is a good target to be used in `x.__iadd__(iterable)`.

That 
----
>>> def f(): x = x + 1
...
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in f
UnboundLocalError: local variable 'x' referenced before assignment
----
is OK, the interpreter sees `x` as local variable (by default inner scope variables shadow those from outer scopes), hence the `UnboundLocalError`
msg412380 - (view) Author: Vedran Čačić (veky) * Date: 2022-02-02 16:59
You've managed to write 3 messages already, without at any point mentioning what _really_ happens when you += something.

    a += b means (is closest to) a = type(a).__iadd__(a, b)

You focus all the time on __iadd__ call, disregarding that its result it assigned back to a in scope.
msg412381 - (view) Author: Marek Scholle (mscholle) Date: 2022-02-02 17:09
> a += b means (is closest to) a = type(a).__iadd__(a, b)

I exchanged several messages, and this is all I needed!
I propose to resolve as "Not a bug"
History
Date User Action Args
2022-04-11 14:59:55adminsetgithub: 90770
2022-02-02 17:41:28eric.smithsetstatus: open -> closed
resolution: not a bug
stage: resolved
2022-02-02 17:09:31mschollesetmessages: + msg412381
2022-02-02 16:59:52vekysetnosy: + veky
messages: + msg412380
2022-02-02 16:49:00mschollesetmessages: + msg412375
2022-02-02 16:38:45eric.smithsetnosy: + eric.smith
messages: + msg412374
2022-02-02 15:33:10mschollesetmessages: + msg412367
2022-02-02 15:19:26steven.dapranosetnosy: + steven.daprano
messages: + msg412366
2022-02-02 15:06:11mschollecreate