We know tuple
s do not support item assignment, but we can perform inplace add
with tuples, however, this creates a new object, as tuples are immutable. for example:
>>> t1 = (1, 2)
>>> id(t1)
154311048
>>> t1 += (3, 4)
>>> t1
(1, 2, 3, 4)
>>> id(t1)
157955320
That's all good. On the other hand, list
s are mutable and do not create new objects while performing inplace add.
Now I was under the impression that inplace add in python was implemented with __iadd__
magic, so:
>>> l1 = [1,2,3]
>>> l1 += [4, 5]
# is same as
>>> l1 = l1.__iadd__([4,5]) # 1
# but, again for lists, this is also same as, simply
>>> l1.__iadd__([4,5]) # 2
I surmise that, as inplace operation is not guaranteed for immutable objects, python assigns l1.__iadd__([4,5])
to l1
, otherwise, for lists, simply calling l1.__iadd__([4,5])
without assigning it back to l1
would modify l1
inplace.
So, with that in mind, I guessed tuples might work like this, i.e.,
>>> t1 = t1.__iadd__((3,4))
But,
Traceback (most recent call last):
File "<ipython-input-12-e8ed2ace9f7f>", line 1, in <module>
t1.__iadd__((1,2))
AttributeError: 'tuple' object has no attribute '__iadd__'
Indeed, '__iadd__' in dir(tuple)
evaluates to False
. Then I thought, may be internally, Python makes a list out of tuples, performs the __iadd__
and converts the result back into tuple
(I don't know why would anyone do that! Moreover, that wouldn't explain the performance benefits of tuples over list) or for immutable objects, __iadd__
might just fallback to __add__
and return the value, but after doing dis
:
>>> from dis import dis
>>> dis('t1 += (3, 4)')
1 0 LOAD_NAME 0 (t1)
2 LOAD_CONST 0 ((3, 4))
4 INPLACE_ADD
6 STORE_NAME 0 (t1)
8 LOAD_CONST 1 (None)
10 RETURN_VALUE
But the bytecode has the instruction INPLACE_ADD
!
For list it goes like:
>>> dis('l1 += [1,2]')
1 0 LOAD_NAME 0 (l1)
2 LOAD_CONST 0 (1)
4 LOAD_CONST 1 (2)
6 BUILD_LIST 2
8 INPLACE_ADD
10 STORE_NAME 0 (l1)
12 LOAD_CONST 2 (None)
14 RETURN_VALUE
Furthermore, in case of list
there is an extra instruction BUILD_LIST
, so tuples
do not build a list either!
As you can see, this is extremely confusing for me. Could someone explain what's going on, how does this work?
INPLACE_ADD
falls back to regular addition with __add__
and __radd__
if __iadd__
doesn't exist or returns NotImplemented
.