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? Как эти несколько назначений в одной строке анализируются и оцениваются? Я неправильно читаю ссылку на язык?

4 ответов


весь кредит идет @MarkDickinson, который ответил на это в комментарии:

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

все target_list productions (т. е. вещи, которые выглядят как 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

Марк Дикинсон объяснил синтаксис того, что происходит, но странные примеры с участием 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

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

первая инструкция толкает 5 в стек.

вторая инструкция дублирует его ... так теперь в верхней части стека есть два 5s

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

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

остальные байткод не прямое отношение.

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

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

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


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

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


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

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