Search code examples
pythonlisttuplesin-place

How does inplace add work in tuples if tuples do not include __iadd__ but still use INPLACE_ADD instruction?


We know tuples 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, lists 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?


Solution

  • INPLACE_ADD falls back to regular addition with __add__ and __radd__ if __iadd__ doesn't exist or returns NotImplemented.