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

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

Это приводит к [1, 6].

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

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


person felipa    schedule 13.03.2017    source источник
comment
@tobias_k Возьмите первый пример. a.pop() возвращает 3 и изменяет a на [1,2]. Теперь оцените a[-1] = a[-1] + 3, и вы получите 5, а не 6.   -  person felipa    schedule 13.03.2017
comment
@felipa Я предполагаю, что Python сначала переводит a[-1] += a.pop() в a[-1] = a[-1] + a.pop(). Вот почему вы получаете 6. a[-1] оценивается перед a.pop(). Если вы измените его на a[-1] = a.pop() + a[-1], вы получите 5   -  person Ma0    schedule 13.03.2017
comment
Все сложно, хороший вопрос, я бы постарался избегать подобных ситуаций.   -  person Elmex80s    schedule 13.03.2017
comment
Несмотря на то, что многочисленные комментарии на этой странице говорят об обратном, обратите внимание, что в целом a += b не совпадает с a = a + b и не гарантируется, что Python будет переведен как таковой. Вызываются разные методы. Сводится ли это к одному и тому же, зависит от типов операндов.   -  person Alex Riley    schedule 13.03.2017
comment
@ajcr Это очень интересно! Не могли бы вы рассказать об этом, пожалуйста, может быть, в ответе?   -  person felipa    schedule 13.03.2017
comment
Я не уверен, подходит ли он для этого ответа, но вы можете найти подробное объяснение -python">здесь. В основном + вызывает метод __add__, тогда как += пытается вызвать метод __iadd__ - они могут иметь одинаковый эффект (например, целые числа) или могут немного отличаться (например, списки).   -  person Alex Riley    schedule 13.03.2017
comment
@Chris_Rands: я попытался тщательно сформулировать свой комментарий, чтобы сказать, что в целом они не эквивалентны и что это зависит от объектов: я согласен, что здесь это достаточно разумно. (Но, с другой стороны, я не уверен, что тот факт, что операторы + и += эквивалентны для целых чисел, является ключом к ответу на этот вопрос: общий результат был бы таким же, если бы были задействованы разные объекты. Я думаю, что порядок вычисления главное.)   -  person Alex Riley    schedule 13.03.2017
comment
@ajcr Решение о том, что находится в RHS, чтобы рассмотреть порядок оценки, кажется тонким, как вы указали.   -  person felipa    schedule 13.03.2017
comment
b = [[5], [3]]; a = b[1]; b[-1] += b.pop(); print (a) еще больше сбивает с толку!   -  person felipa    schedule 13.03.2017
comment
PSA: Никогда не пишите код, основанный на таких деталях. Даже если он работает, это плохой код. Если вы изолируете побочные эффекты на отдельной строке (это примерно равно одному изменению состояния на строку), чтение и изменение вашего кода будет намного проще. В этом случае вы должны сделать a = [1, 2, 3]; temp = a.pop(); a[-1] = 2 * temp или a = [1, 2, 3]; temp = a.pop(); a[-1] += temp, в зависимости от того, что вы хотели сделать. Это делает ваш предполагаемый порядок оценки явным, и его легче получить правильно.   -  person jpmc26    schedule 14.03.2017


Ответы (5)


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

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]
person Fallen    schedule 13.03.2017
comment
в RHS слева направо Забавный факт: хотя операторы, конечно, оцениваются по w.r.t. приоритет оператора, фактические выражения, по-видимому, не являются, например. в f() + g() * h() функции оцениваются в порядке f, затем g, затем h. Например, a.pop() + a.pop() * a.pop() с a = [3, 2, 1] дает 7 (1 + 2 * 3) - person tobias_k; 13.03.2017
comment
@tobias_k хорошо, оператор * оценивается перед оператором +. Может быть полезно рассматривать вызов функции как другой оператор с более высоким приоритетом, чем *. - person Random832; 13.03.2017
comment
@ Random832 Эта часть ясна; что меня поставило в тупик, так это то, что в (a+(b+(c+(d+(...))))) сначала оценивается a, затем b и т. д. Но я думаю, что это всего лишь моя человеческая точка зрения, так как я визуально анализирую выражение и определить самую внутреннюю часть, чтобы оценить это в первую очередь, поэтому мне не нужно хранить столько в памяти (буквально). Компьютер, конечно, может просто вычислить выражение слева направо и поместить промежуточные результаты в стек. на это стоит обратить внимание, поскольку у других может быть такая же неверная интуиция. - person tobias_k; 13.03.2017
comment
@tobias_k: Многие языки делают это, с заметное исключение для C и C++. - person Kevin; 13.03.2017
comment
@Rob a[-1] оценивается до того, как значение будет удалено, что дает значение 3. Затем появляется pop, также дающее значение 3. Таким образом, математика 3+3. Это связано с тем, что в RHS мы работаем слева направо, оценивая каждую часть отдельно, а затем занимаясь математикой. - person coderforlife; 14.03.2017
comment
@tobias_k Общее правило во многих языках заключается в том, что круглые скобки используются для переопределения приоритета операторов, но не порядка оценки. Таким образом, это просто влияет на то, как сгруппированы различные операции. Таким образом, самый внутренний + будет выполнен первым, но это не значит, что d должен быть оценен первым. - person Barmar; 14.03.2017

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

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

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

person Chris_Rands    schedule 13.03.2017
comment
a[-1] += a.pop() является сокращением для a[-1] = a[-1] + a.pop() верно только потому, что a - это список целых чисел, как я теперь узнал. Это стоит упомянуть. - person felipa; 13.03.2017
comment
@felipa Хорошо, да, я добавил правку, обратите внимание, если бы a был списком строк или кортежей, он также вел бы себя так же, как целочисленный случай (он отличается только для изменяемых объектов) - person Chris_Rands; 13.03.2017

Давайте посмотрим на вывод 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 это выглядит немного иначе, но похоже.

person tobias_k    schedule 13.03.2017
comment
Классная штука с dis.dis, не знал о такой! - person ppasler; 13.03.2017

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

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. Этот ответ просто дополняет упомянутый там общий принцип.

person MSeifert    schedule 13.03.2017

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

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

Заказ:

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

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

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

Работает как положено

  1. оценить a[0] после =
  2. pop()
  3. добавление
  4. назначение

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

person ppasler    schedule 13.03.2017