Как inplace add работает в кортежах, если кортежи не включают __iadd__, но по-прежнему используют инструкцию INPLACE_ADD?

Мы знаем, что tuple не поддерживает назначение элементов, но мы можем выполнять inplace add с кортежами, однако при этом создается новый объект, поскольку кортежи неизменяемы. Например:

>>> t1 = (1, 2)
>>> id(t1)
154311048
>>> t1 += (3, 4)
>>> t1
(1, 2, 3, 4)
>>> id(t1)
157955320

Это все хорошо. С другой стороны, list являются изменяемыми и не создают новых объектов при выполнении добавления на месте.

Теперь у меня сложилось впечатление, что добавление на месте в python было реализовано с помощью магии __iadd__, поэтому:

>>> 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

Я предполагаю, что, поскольку работа на месте не гарантируется для неизменяемых объектов, python присваивает l1.__iadd__([4,5]) l1, иначе для списков простой вызов l1.__iadd__([4,5]) без назначения его обратно l1 изменит l1 на месте.

Итак, имея это в виду, я предположил, что кортежи могут работать так, т.е.

>>> t1 = t1.__iadd__((3,4))

Но,

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__'

Действительно, '__iadd__' in dir(tuple) оценивается как False. Затем я подумал, может быть, внутри Python составляет список из кортежей, выполняет __iadd__ и преобразует результат обратно в tuple (я не знаю, зачем кому-то это делать! Более того, это не объясняет преимущества кортежей в производительности). над списком) или для неизменяемых объектов __iadd__ может просто вернуться к __add__ и вернуть значение, но после выполнения 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

Но в байт-коде есть инструкция INPLACE_ADD! Для списка это выглядит так:

>>> 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

Кроме того, в случае list есть дополнительная инструкция BUILD_LIST, так что tuples тоже не строит список!

Как видите, меня это очень смущает. Может кто-нибудь объяснить, что происходит, как это работает?


person Cyttorak    schedule 26.02.2020    source источник


Ответы (1)


INPLACE_ADD возвращается к обычному сложению с __add__ и __radd__, если __iadd__ не существует или возвращает NotImplemented.

person user2357112 supports Monica    schedule 26.02.2020
comment
Спасибо. Не могли бы вы также объяснить, почему существует необходимость в BUILD_LIST при добавлении list на месте? Если нет, то могу задать отдельный вопрос, если хотите. - person Cyttorak; 26.02.2020
comment
@SayandipDutta Создает список [1,2] для передачи в качестве второго аргумента INPLACE_ADD. - person Dan D.; 26.02.2020
comment
Да, но почему он не может сделать что-то эквивалентное этому 2 LOAD_CONST 0 ((3, 4)), например: 2 LOAD_CONST 0 ([3, 4])? - person Cyttorak; 26.02.2020