For immutable types, a = a + b and a += b do behave exactly the same. For mutable ones, the problem is that of the three statements (taking lists as an example)
a = a + b # (1)
a += b # (2)
a.extend(b) # (3)
a reasonable programmer can (separately) expect both (1) and (2) to be the same or (2) and (3) to be the same, but these are in fact mutually exclusive. Python’s designers chose to make (2) and (3) the same, but other languages may reasonably choose (1) and (2) instead. As far as I can see, this problem is inherent in having references to mutable objects as values in the language.That doesn’t mean that knowing an implementation detail can’t be useful, just that I feel the way (1) and (2) for lists differ (“semantics”) is of a different nature from the way (1) and (2) for strings differ (“implementation”) and worth keeping in a different mental drawer.
It's possible (likely? ) that I'm missing something, but why are they exclusive? In principle, I would have thought that an optimizing compiler could rewrite (1) into (3) and thus give all three the same semantics.
(I'm not speaking about what would be possible for Python in particular, just in general terms)
If you start off with:
a=c=[1,2,3]
b=[4,5]
then a=a+b
with semantics that change 'a' would also change the object pointed to by 'c', which I think isn't what most people would expect (and is not the semantics specified by Python).The other forms explicitly change the object pointed to by 'a'.
a = [1, 2]; b = [3, 4]
c = a
a += b
print(c) # [1, 2] — assignment was a copy
print(a) # [1, 2, 3, 4]
So every list assignment would have to be a copy for that mental model not to break easily.And certainly, there are languages that do that.
Just not Python — for performance reasons, you need to be explicit about copying large chunks of memory, though it includes a fair number of footguns like concatenation of strings and lists, or even slicing — eg. "for a in long_list[:-2]:" will make a duplicate of long_list in memory.
Eg.
a = [1, 2]
b = [3, 4]
c = a
Would you really expect a += b and a = a + b to behave the same, and what would c be in that case?
Yes, I would, and it really is worth noting that they don't.
c would be [1,2,3,4], and it would be an alias of a. But both of those points are also true of the way things actually do work. I don't really understand your question.
a = [1]
c = a
a = a + a
Now, what is c ? what is a?
Then try again with a+=a
>>> a = [1, 2]
>>> b = [3, 4]
>>> c = a
>>> a = a + b
>>> c
[1, 2]That is not true.
>>> a = [1, 2]
>>> b = [3, 4]
>>> c = a
>>> a += b
>>> c
[1, 2, 3, 4]
And contrast that with:
>>> a = [1, 2]
>>> b = [3, 4]
>>> c = a
>>> a = a + b
>>> c
[1, 2]
If "c" remains an alias of "a" after you've assigned something else to "a", then there is no way in language to keep a reference to what "a" was.
Eg. what you seem to be proposing is that assignment works like this:
a = 4
old_a = a
a = 5
print(old_a) # prints out 5.
Python is like it is for practical performance reasons: lists and dicts are by-reference because copying them is expensive. You've got to do it explicitely with .copy() or deepcopy module. That's a basic lesson on Python I was referring to. And sure, it could have been designed the other way around, when you'd have to introduce a "refcopy" or "&" operator when performance is needed, but it wasn't designed that way. There are other languages which do that.
I can see the point of "a += b" unexpectedly updating in place, but just upon reading it, I'd struggle to figure out what's supposed to happen. In a code review, I'd suggest a developer to switch to "a.extend(b)".
Basically, if they were to behave the same, I don't think an argument can be made for the common result of "c" to evaluate to [1, 2, 3, 4], but rather to [1, 2].
I found the book Fluent Python to be a great introduction to the ideas behind abstraction in Python.
Apparently, it's cadged from the Art of the Metaobject Protocol, which is a great book (which annoyingly enough, is not available in ebook form, which is a shame as typing loads of code from a dead-tree book is time consuming).
"Implementations of operator traits should be unsurprising in their respective contexts, keeping in mind their usual meanings"
Which seems like reasonable advice.