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

Задания с несколькими присваиваниями Python в одной строке

(Не волнуйтесь, это не вопрос о распаковке кортежей.)

В python оператор типа foo = bar = baz = 5 присваивает переменные foo, bar и baz равным 5. Он присваивает эти переменные слева направо, что может быть доказано более непримиримыми примерами, например

>>> foo[0] = foo = [0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined
>>> foo = foo[0] = [0]
>>> foo
[[...]]
>>> foo[0]
[[...]]
>>> foo is foo[0]
True

Но ссылка на язык python утверждает, что операторы присваивания имеют форму

(target_list "=")+ (expression_list | yield_expression)

и при присваивании сначала выполняется оценка expression_list, а затем выполняется присваивание.

Итак, как может быть строка foo = bar = 5, учитывая, что bar = 5 не является expression_list? Как эти множественные присвоения на одной строке анализируются и оцениваются? Я неправильно читаю ссылку на язык?

4b9b3361

Ответ 1

Все кредиты принадлежат @MarkDickinson, которые ответили на это в комментарии:

Обратите внимание на + в (target_list "=")+, что означает одну или несколько копий. В foo = bar = 5 есть две производные (target_list "="), а часть expression_list - это просто 5

Все target_list производные (т.е. вещи, которые выглядят как foo =) в операторе присваивания, назначаются слева направо на expression_list в правом конце инструкции после того, как expression_list получает оценку.

И, конечно же, обычный синтаксис присваивания "кортеж-распаковка" работает в этом синтаксисе, позволяя вам делать что-то вроде

>>> foo, boo, moo = boo[0], moo[0], foo[0] = moo[0], foo[0], boo[0] = [0], [0], [0]
>>> foo
[[[[...]]]]
>>> foo[0] is boo
True
>>> foo[0][0] is moo
True
>>> foo[0][0][0] is foo
True

Ответ 2

Марк Дикинсон объяснил синтаксис происходящего, но странные примеры с участием foo показывают, что семантика может быть контринтуитивной.

В C, = является право-ассоциативным оператором, который возвращает в качестве значения RHS присваивания, поэтому, когда вы пишете x = y = 5, сначала оценивается y=5 (присваивая значение 5 y в процессе) и это значение (5) затем присваивается x.

Прежде чем я прочитал этот вопрос, я наивно предположил, что примерно то же самое происходит в Python. Но в Python = не является выражением (например, 2 + (x = 5) является синтаксической ошибкой). Таким образом, Python должен выполнять несколько назначений по-другому.

Мы можем разбирать, а не гадать:

>>> import dis
>>> dis.dis('x = y = 5')
  1           0 LOAD_CONST               0 (5)
              3 DUP_TOP
              4 STORE_NAME               0 (x)
              7 STORE_NAME               1 (y)
             10 LOAD_CONST               1 (None)
             13 RETURN_VALUE

См. this для описания инструкций байтового кода.

Первая инструкция нажимает 5 на стек.

Вторая команда дублирует ее - так что теперь верхняя часть стека имеет два 5s

STORE_NAME(name) "Реализует имя = TOS" в соответствии с документами байтового кода

Таким образом, STORE_Name(x) реализует x = 5 (5 поверх стека), выбирая это 5 из стека, когда он идет, после чего STORE_Name(y) реализует y = 5 с другими 5 в стеке.

Остальная часть байт-кода не имеет прямого значения здесь.

В случае foo = foo[0] = [0] байт-код более сложный из-за списков, но имеет принципиально схожую структуру. Главное наблюдение заключается в том, что как только список [0] создается и помещается в стек, тогда команда DUP_TOP не помещает в стек другую копию [0], вместо этого она помещает другую ссылку в список. Другими словами, на этом этапе два верхних элемента стека являются псевдонимами для одного и того же списка. Это наиболее отчетливо видно в несколько более простом случае:

>>> x = y = [0]
>>> x[0] = 5
>>> y[0]
5

Когда выполняется foo = foo[0] = [0], список [0] сначала присваивается foo, а затем псевдоним того же списка присваивается foo[0]. Вот почему это приводит к тому, что foo является круглой ссылкой.

Ответ 3

bar = 5 не является выражением. Множественное присвоение представляет собой отдельный оператор из оператора присваивания; выражение - это все справа от самого правого =.

Хороший способ подумать о том, что самый правый = является основным разделителем; все справа от него происходит слева направо, и все, что слева от него, также происходит слева направо.

Ответ 4

https://docs.python.org/3/reference/simple_stmts.html#grammar-token-assignment_stmt

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