Почему цепное задание работает таким образом?

Я нашел назначение a = a[1:] = [2] в статье. Я попробовал это в python3 и python2; все работает, но я не понимаю, как это работает. = здесь не похоже на C; C процессы = справа налево. Как python обрабатывает оператор =?

Ответ 1

Per языковые документы при назначении:

Оператор присваивания оценивает список выражений (помните, что это может быть одно выражение или список, разделенный запятыми, последний из которых имеет кортеж) и присваивает единственный результирующий объект каждому из целевых списков слева направо.

В этом случае a = a[1:] = [2] имеет список выражений [2] и два "целевых списка", a и a[1:], где a - самый левый список целей.

Вы можете видеть, как это происходит, глядя на разборку:

>>> import dis
>>> dis.dis('a = a[1:] = [2]')
  1           0 LOAD_CONST               0 (2)
              2 BUILD_LIST               1
              4 DUP_TOP
              6 STORE_NAME               0 (a)
              8 LOAD_NAME                0 (a)
             10 LOAD_CONST               1 (1)
             12 LOAD_CONST               2 (None)
             14 BUILD_SLICE              2
             16 STORE_SUBSCR
             18 LOAD_CONST               2 (None)
             20 RETURN_VALUE

(Последние две строки разборки могут быть проигнорированы, dis делает оболочку функции для демонтажа строки)

Важно отметить, что когда вы выполняете x = y = some_val, some_val загружается в стек (в этом случае LOAD_CONST и BUILD_LIST), тогда запись стека дублируется и назначается из слева направо, до заданных целей.

Итак, когда вы это сделаете:

a = a[1:] = [2]

он делает две ссылки на новый list, содержащий 2, а первое действие - это STORE одна из этих ссылок на a. Затем он сохраняет вторую ссылку на a[1:], но так как назначение среза мутирует a, оно должно снова загрузить a, которое получает только list. К счастью, list устойчив к присваиванию self-slice, или у нас были бы проблемы (это было бы навсегда чтение значения, которое оно просто добавило, чтобы добавить в конец до тех пор, пока мы не исчерпали память и не разбились); как есть, он ведет себя так, как копия [2] была назначена для замены любого и всех элементов из индекса один дальше.

Конечный результат эквивалентен тому, что вы сделали:

_ = [2]
a = _
a[1:] = _

но он избегает использования имени _.

Чтобы быть понятным, демонтаж аннотируется:

Сделать список [2]:

  1           0 LOAD_CONST               0 (2)
              2 BUILD_LIST               1

Сделайте копию ссылки на [2]:

              4 DUP_TOP

Выполнить хранилище до a:

              6 STORE_NAME               0 (a)

Выполнить хранилище до a[1:]:

              8 LOAD_NAME                0 (a)
             10 LOAD_CONST               1 (1)
             12 LOAD_CONST               2 (None)
             14 BUILD_SLICE              2
             16 STORE_SUBSCR

Ответ 2

То, как я понимаю такие назначения, состоит в том, что это эквивалентно

temp = [2]
a = temp
a[1:] = temp

Результирующее значение [2, 2] согласуется с этой интерпретацией.