Задания с несколькими присваиваниями 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
? Как эти множественные присвоения на одной строке анализируются и оцениваются? Я неправильно читаю ссылку на язык?
Ответы
Ответ 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
Оператор присваивания оценивает список выражений (помните, что это может быть одно выражение или список, разделенный запятыми, последний из которых имеет кортеж) и присваивает единственный результирующий объект каждому из целевых списков слева направо.