(心配しないでください、これはタプルのアンパックについての別の質問ではありません。)
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 "=")+
に注意してください。これは、1つ以上のコピーを意味します。foo = bar = 5
には、2つの(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
Mark Dickinsonは何が起こっているかの構文を説明しましたが、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をスタックにプッシュします。
2番目の命令はそれを複製するので、スタックの最上部には2つの5があります
STORE_NAME(name)
"バイト名= TOS"のバイトコードドキュメントによる
したがって、STORE_Name(x)
はx = 5
(スタックの最上部にある5)を実装し、その5をスタックからポップします。その後、STORE_Name(y)
はスタック上の他の5と一緒にy = 5
を実装します。
ここでは、残りのバイトコードは直接関係ありません。
foo = foo[0] = [0]
の場合、バイトコードはリストのためにより複雑になりますが、基本的に類似した構造を持っています。重要な点は、リスト[0]
が作成されてスタックに配置されると、命令DUP_TOP
はスタックに[0]
の別のcopyを配置せず、別のreferenceリストへ。つまり、その段階では、スタックの上位2つの要素は同じリストのエイリアスです。これは、やや単純な場合に最もはっきりとわかります。
>>> 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
割り当てステートメントは式リストを評価し(これは単一の式またはコンマ区切りリストであり、後者はタプルを生成することに注意してください)、単一の結果オブジェクトを左から右に各ターゲットリストに割り当てます。