1

list & tuples behavior on + and += operator overloading:

this fails:

>>> [1,2] + (3,4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate list (not "tuple") to list

but this works:

>>> l = [1,2]
>>> l += (3,4)
>>> l
[1, 2, 3, 4]

with set & frozenset things behave differently:

this works:

>>> {1,2} | frozenset({3,4})
{1, 2, 3, 4}

this also works:

>>> s = {1,2}
>>> s |= frozenset({3,4})
>>> s
{1, 2, 3, 4}

Why [1,2] + (3,4) dont work similarly to {1,2} | frozenset({3,4})?

Why the two differ? is there a reason for this? is it a backward compatibility thing or something related to internals?

I'm more interested in why its implemented like this not what technically happens under the hood. I suspect that the Python language designed with some careful thinking and there is a reason for this difference which i want to understand.

ShmulikA
  • 3,468
  • 3
  • 25
  • 40

1 Answers1

0

The main difference is the implementation which isn't consistent and very annoying.

In this example:

>>> l = [1,2]
>>> l += (3,4)

One would expect l += (3, 4) to translate to l = l + (3, 4) and fail, but the list.__iadd__/set.__iadd__ implementation is different and mutates the original object. But it actually translates to roughly:

l.extend((3, 4))

And by caliing list.extend it can be called with (probably) any iterable:

>>> l = [1]
>>> l += {11, 31, 21} # <--- this should be a lot more troubling (no order)
>>> l
[1, 11, 21, 31] # worked, but obviously no order guarantees

This is probably done for efficiency reasons, but it is implicit and very error-prone. It makes this idiomatic code pattern:

l = []
for items in items_generator:
    l += items

Scale much better without the user having to think about it.

The custom implementations (iadd(x, y), or_(x, y)) for native data structures are exceptions made for performance reasons.

Reut Sharabani
  • 30,449
  • 6
  • 70
  • 88
  • thanks @Reut but im looking for a reason why its like this in terms of design decision of the python language. this works fine: `x = [1,2];y=[3,4];x+y` creating a new 3rd list so its possible to implement it i guess there is another decision (maybe strongly type concerns?) – ShmulikA Sep 22 '19 at 11:31
  • @ShmulikA I believe both are done for efficiency. This way the `__iadd__` implementations do not need to copy the list (since python has no structural sharing, it has to copy the entire list to do `l = l + [1]`). – Reut Sharabani Sep 22 '19 at 11:35
  • 1
    thanks for the technical explanation but its not my question. `[1] + (1,)` fails while `[1] + [1]` works - regardless of efficiency. its design decision to not support this operation. `x + [1]` works. why not support `list + tuple` and only support `list + list` while += supported by both. why its differ from `set + frozenset`? its not a matter of mutability (set is mutable) or efficiency (it returns TypeError) – ShmulikA Sep 22 '19 at 12:32
  • @ShmulikA I've elaborated my answer a bit, but it practically remains the same. The way I see it, exceptions were made in the implementations of native data structures for performance reasons of idiomatic code. Maybe I just don't understand the question. Good luck anyway :) – Reut Sharabani Sep 22 '19 at 12:50