Python assignment operator differs from non assignment
Asked Answered
R

2

20

I have face this weird behavior I can not find explications about.

MWE:

l = [1]
l += {'a': 2}
l
[1, 'a']
l + {'B': 3}
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: can only concatenate list (not "dict") to list

Basically, when I += python does not raise an error and append the key to the list while when I only compute the + I get the expected TypeError.

Note: this is Python 3.6.10

Ravi answered 27/4, 2020 at 10:48 Comment(7)
Check this https://mcmap.net/q/117180/-when-is-quot-i-x-quot-different-from-quot-i-i-x-quot-in-pythonAverell
See bugs.python.org/issue9314 - "inconsistent result when concatenating list with iterators". Also note += and so on are called "Augmented assignments" from the PEP introducing them, PEP 203.Cyclosis
That is something strangeMertz
I think the bug link by @Cyclosis has a statment that explains it well. When a is mutable, a += b updates it in-place, so there is no ambiguity: the type of a cannot change. When you do a + b, there is no reason to treat a as more deserving than b when selecting the type of the result.Longinus
@ChrisDoyle that is the exact reasoning. It is not "a bug", it's a mechanism that keeps the interpreter from guessing.Bringingup
Yeah i just added it as a comment here to make it clear why the behavior happens as there is quite a lot of info in the link. But yes its not a bug its expected behavior, the comment just helps explain why it works like it doesLonginus
@ChrisDoyle that's the case. see my detailed answer below. cheersNila
N
17

l += ... is actually calling object.__iadd__(self, other) and modifies the object in-place when l is mutable

The reason (as @DeepSpace explains in his comment) is that when you do l += {'a': 2} the operation updates l in place only and only if l is mutable. On the other hand, the operation l + {'a': 2} is not done in place resulting into list + dictionary -> TypeError.


(see here)


l = [1]
l = l.__iadd__({'a': 2})
l
#[1, 'a']

is not the same as + that calls object.__add__(self, other)

l + {'B': 3}
TypeError: can only concatenate list (not "dict") to list
Nila answered 27/4, 2020 at 10:53 Comment(6)
This should also explain the reasoning. l += ... updates l in place, so the type of the result is clear (= the type of l). l + ... is ambiguous because it is not in place, so the type of the resulting new object is not clear (should it be the type of l or should it be the type of ...?)Bringingup
of course! I added this to my answer. The l += ... operates in-placeNila
@seralouk, do you mind sharing which tool you used to found out which function was invoked ? Inspect, pdb etc ?Mclaurin
the documentation: docs.python.org/3/reference/datamodel.html#object.__iadd__ and some programming knowledge e.g. dictionary are mutables.Nila
@Bringingup the reasoning makes sense, but __iadd__ is not guaranteed to return the same object. See number += 2. Also, from docs: These methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self)Stringhalt
@CapiEtheriel It's not about returning the same object, but about returning the same type. Also, int is not a good example as it is a special case, a = 1 ; a += 2.0, now a is float. But, int is immutable to begin with, so "in-place" has no meaning.Bringingup
M
1

So as the authors say this is not a bug. When you Do a += b it is like b come to a's house and changing it the way that a like it to be. what the Authors say is when you do a + b it cannot be decided which one's style will get prioritized. and no one knows where will the result of a + b will go until you execute it. So you can't decide whose style it would be. if it is a style it would be [1, 'a']'s, and if it is b style it would be an error. and therefore it cannot be decided who will get the priority. So I don't personally agree with that statement. because when you take the call stack a is in a higher place than b. when there is a expression like a + b you first call a.__add__(self, other) if a.__add__ is NotImplemented (in this case it is implemented). then you call a.__radd__(self, other). which means call other.__add__ in this case b.__add__. I am telling this based on the place of the call stack and the python community may have more important reasons to do this.

Metropolitan answered 8/5, 2020 at 7:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.