引き抜く時が来ましたdis
。
>>> import dis
>>> dis.dis(foo)
2 0 LOAD_GLOBAL 0 (Bar)
3 CALL_FUNCTION 0
6 DUP_TOP
7 STORE_FAST 0 (bar)
10 STORE_FAST 1 (initial_bar)
3 13 SETUP_LOOP 32 (to 48)
>> 16 LOAD_GLOBAL 1 (True)
19 POP_JUMP_IF_FALSE 47
4 22 LOAD_GLOBAL 0 (Bar)
25 CALL_FUNCTION 0
28 STORE_FAST 2 (next_bar)
5 31 LOAD_FAST 2 (next_bar)
34 DUP_TOP
35 STORE_FAST 0 (bar)
38 LOAD_FAST 0 (bar)
41 STORE_ATTR 2 (next_bar)
44 JUMP_ABSOLUTE 16
>> 47 POP_BLOCK
6 >> 48 LOAD_FAST 1 (initial_bar)
51 RETURN_VALUE
これをよく見ると、重要な行 (5 行目、左側の 31 から 47 の位置の数字を参照) で次のようになっていることがわかります。
next_bar
(31) を 2 回 (34)ロードします。
- それ (スタック上の最初のコピー) を
bar
(35) に書き込みます。
- それ (スタック上の 2 番目のコピー) を
bar.next_bar
(38, 41) に書き込みます。
これは、最小限のテスト ケースでより明確に見られます。
>>> def a():
... b = c = d
...
>>> dis.dis(a)
2 0 LOAD_GLOBAL 0 (d)
3 DUP_TOP
4 STORE_FAST 0 (b)
7 STORE_FAST 1 (c)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
それが何をしているのか見てください。これは、b = c = d
実際には と同等であることを意味しb = d; c = d
ます。通常、これは問題になりませんが、最初に言及された場合は問題になります。つまり、クリティカルラインでは、
bar = bar.next_bar = next_bar
と同等ではありません
bar.next_bar = next_bar
bar = next_bar
しかし、むしろ
bar = next_bar
bar.next_bar = next_bar
実際、これは Python ドキュメントのセクション 6.2、単純なステートメント、代入ステートメントに記載されています。
割り当てステートメントは式リストを評価し (これは単一の式またはコンマ区切りのリストであり、後者はタプルを生成することに注意してください)、単一の結果オブジェクトを各ターゲット リストに左から右に割り当てます。
そのセクションには、このケースに適用される関連する警告もあります。
警告: 代入の定義は、左側と右側の間のオーバーラップが「安全」であることを暗示していますが (たとえばa, b = b, a
、2 つの変数を交換します)、代入先変数のコレクション内でのオーバーラップは安全ではありません! たとえば、次のプログラムは次のように出力します[0, 2]
。
x = [0, 1]
i = 0
i, x[i] = 1, 2
print x
それを実行することは可能でbar.next_bar = bar = next_bar
あり、それは当初の望ましい結果をもたらしますが、後でコードを読まなければならない人 (元の作者を含む!) を気の毒に思ってください。ティムがそれらのことを考えていたら、確かに使っていたでしょう。
明示的なものは、紛らわしいコーナーケースよりも優れています。