Still one of the oldest #Python oddities I've ever seen 🐍🤔
>>> a = ([],)
>>> a[0] += ["what"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> a
(['what'],)
Note that an exception was raised but the operation also worked! 😳 #pythonoddity
@chris @treyhunner @s_gruppetta
It's weirder than that!
`a[0] = a[0] + ["what"]` reports the error but doesn't have the side effect.
`a[0] += ["what"]` reports the error and DOES have the side effect!
I'm shocked both that the failed assignment has a side effect and that these do not behave the same way. Can anyone explain?
@peterdrake @chris @treyhunner Oops, markdown error (shame there's no preview), the id for `b` in the first example should be on a new line, of course
@peterdrake @chris @treyhunner This shows that `+=` is not quite just syntactic sugar for the separate `=` and `+`. One creates a new object; the other doesn't
@s_gruppetta @peterdrake @treyhunner I think Stephen nailed it with the observation that one is an `__add__` that makes a new `list`, versus an `__iadd__` that modifies the `list`
@s_gruppetta @chris @treyhunner Fascinating!
I can imagine efficiency reasons for these being different.
It still seems wrong that __iadd__ for tuples makes a change and *then* throws an error. Is there a reason for this behavior?
@peterdrake @s_gruppetta @chris it's actually __iadd__ on a list that's being used here.
a[0] += ["huh"]
Checks if a[0] has a __iadd__ method, which of it does because it's a list. So that line is translated to:
a[0] = a[0].__iadd__(["huh"])
The list __iadd__ method succeeds.
But then assigning to a[0] (which happens after the right-hand side of that assignment) fails.
@peterdrake @chris As @s_gruppetta said, it's all about how += works on lists.
+= on mutable objects is a combination of __iadd__ and =,
The __iadd__ call succeeds but the assignment fails
Interestingly += on tuples doesn't work this way! The += operator falls back to __add__ followed by an assignment on immutable objects.
So while
a += b
Is the same as
a = a + b
With a tuple or a string.
Those two lines aren't the same on a list!
This oddity is in the docs too: https://docs.python.org/3.5/faq/programming.html#faq-augmented-assignment-tuple-error
@peterdrake @chris @s_gruppetta
My #PyConUS2022 talk was on #Python oddities and had this #pythonoddity as the punchline
@treyhunner @chris @s_gruppetta Ah, that explains it!
Definitely still odd.
@undefined @treyhunner @chris @s_gruppetta This has implications beyond tuples. If `a` is a list and we
`b = a`
then
`a = a + ...`
does not modify `b` but
`a += ...`
does.
@peterdrake @chris @s_gruppetta right!
I usually explain to my students by noting that __iadd__ stands for in-place addition and in-place operations are intended to mutate the original object when possible.
Though += and friends are most often used on strings and numbers, which are immutable so we don't see that behavior most of the time...
I do wonder whether it might have been more sensible to design Python to never mutate with augmented assignments, even on mutable objects.
@peterdrake @chris @s_gruppetta though this issue comes up so rarely it may not even be worth considering "what if they'd designed things differently" 🤷
Almost any time I might use += on a list, I tend to use the extend method instead because += on a list does the same thing as the list extend method anyway.
@treyhunner @chris @s_gruppetta I presume this feature was added so that programmers wouldn't have to think about the most efficient way of extending a list.
In the corner, the functional programming people are smugly examining their cuticles.
@peterdrake @chris @treyhunner Trey is the master at these things, but here's my attempt.
In the first one, the expression after the `=` is executed first; this is fine as you're adding one list to another (using `list.__add__()`). This creates a new object. Then, there's an attempt to assign this new object as the first item in `a` which fails since `a` is a tuple.
In the second, the assignment and addition are attempted together using the `__iadd__()` dunder method, rather than `__add__()`. No new object is created.
Here's a different example which may shed some light:
`>>> b = [2]`
`>>> c = [3]`
`>>> id(b)`
`4407498688`
`>>> b = b + c`
`>>>`id(b)`
`4407508864`
`b` has a different id after the assignment, so it's a different object to the original `b`
now, in a new session:
`>>> b = [2]`
`>>> c = [3]`
`>>> id(b)`
`4394855808`
`>>> b += c`
`>>> id(b)`
`4394855808`
`b` is now the same object as it was before (same id)