Подтвердить что ты не робот

Каков порядок оценки в python при использовании pop(), list [-1] и + =?

a = [1, 2, 3]
a[-1] += a.pop()

В результате получается [1, 6].

a = [1, 2, 3]
a[0] += a.pop()

В результате получается [4, 2]. Какой порядок оценки дает эти два результата?

4b9b3361

Ответ 1

RHS сначала, а затем LHS. И с любой стороны, порядок оценки оставлен вправо.

a[-1] += a.pop() совпадает с a[-1] = a[-1] + a.pop()

a = [1,2,3]
a[-1] = a[-1] + a.pop() # a = [1, 6]

Посмотрите, как изменяется поведение при изменении порядка операций в RHS,

a = [1,2,3]
a[-1] = a.pop() + a[-1] # a = [1, 5]

Ответ 2

Ключевым понятием является то, что a[-1] += a.pop() является синтаксическим сахаром для a[-1] = a[-1] + a.pop(). Это верно, потому что += применяется к неизменяемому объекту (здесь int), а не к изменяемому объекту (соответствующий вопрос здесь).

Сначала оценивается правая часть (RHS). В RHS эквивалентный синтаксис a[-1] + a.pop(). Во-первых, a[-1] получает последнее значение 3. Во-вторых, a.pop() return 3. 3 + 3 - 6.

В левой части (LHS) a теперь [1,2] из-за мутации in-place, уже примененной list.pop(), и поэтому значение a[-1] изменяется от 2 до 6.

Ответ 3

Посмотрим на вывод dis.dis для a[-1] += a.pop() 1):

3    15 LOAD_FAST            0 (a)                             # a,
     18 LOAD_CONST           5 (-1)                            # a, -1
     21 DUP_TOP_TWO                                            # a, -1, a, -1
     22 BINARY_SUBSCR                                          # a, -1, 3
     23 LOAD_FAST            0 (a)                             # a, -1, 3, a
     26 LOAD_ATTR            0 (pop)                           # a, -1, 3, a.pop
     29 CALL_FUNCTION        0 (0 positional, 0 keyword pair)  # a, -1, 3, 3
     32 INPLACE_ADD                                            # a, -1, 6
     33 ROT_THREE                                              # 6, a, -1
     34 STORE_SUBSCR                                           # (empty)

Значение различных инструкций указано здесь.

Сначала LOAD_FAST и LOAD_CONST загрузите a и -1 в стек, а DUP_TOP_TWO дублирует два, прежде чем BINARY_SUBSCR получит значение индекса, что приведет к a, -1, 3 в стеке. Затем он снова загружает a, а LOAD_ATTR загружает функцию pop, которая вызывается без аргументов с помощью CALL_FUNCTION. Стек теперь a, -1, 3, 3, а INPLACE_ADD добавляет два верхних значения. Наконец, ROT_THREE поворачивает стек до 6, a, -1 в соответствии с порядком, ожидаемым STORE_SUBSCR, и значение сохраняется.

Итак, короче, текущее значение a[-1] оценивается перед вызовом a.pop(), и результат добавления затем сохраняется обратно к новому a[-1], независимо от его текущего значения.


1) Это разборка для Python 3, слегка сжатая для лучшей подгонки на странице, с добавленным столбцом, показывающим стек после # ...; для Python 2 это выглядит немного иначе, но похоже.

Ответ 4

Использование тонкой обертки вокруг списка с отладочными отпечатками печати можно использовать для отображения порядка оценки в ваших случаях:

class Test(object):
    def __init__(self, lst):
        self.lst = lst

    def __getitem__(self, item):
        print('in getitem', self.lst, item)
        return self.lst[item]

    def __setitem__(self, item, value):
        print('in setitem', self.lst, item, value)
        self.lst[item] = value

    def pop(self):
        item = self.lst.pop()
        print('in pop, returning', item)
        return item

Когда я сейчас запустил ваш пример:

>>> a = Test([1, 2, 3])
>>> a[-1] += a.pop()
in getitem [1, 2, 3] -1
in pop, returning 3
in setitem [1, 2] -1 6

Итак, он начинается с получения последнего элемента, который равен 3, затем выдает последний элемент, который также является 3, добавляет их и перезаписывает последний элемент вашего списка с помощью 6. Таким образом, окончательный список будет [1, 6].

И во втором случае:

>>> a = Test([1, 2, 3])
>>> a[0] += a.pop()
in getitem [1, 2, 3] 0
in pop, returning 3
in setitem [1, 2] 0 4

Теперь этот первый элемент (1) добавляет его к всплываемому значению (3) и перезаписывает первый элемент с помощью суммы: [4, 2].


Общий порядок оценки уже объясняется @Fallen и @tobias_k. Этот ответ просто дополняет общий принцип, упомянутый там.

Ответ 5

Для конкретного примера

a[-1] += a.pop() #is the same as 
a[-1] = a[-1] + a.pop() # a[-1] = 3 + 3

Order:

  • оцените a[-1] после =
  • pop(), уменьшая длину a
  • Добавление
  • Назначение

Дело в том, что a[-1] становится значением a[1] (было a[2]) после pop(), но это происходит до назначения.

a[0] = a[0] + a.pop() 

Работает как ожидалось

  • оцените a[0] после =
  • pop()
  • Добавление
  • Назначение

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