タプル同士の比較、そう思っていた時期が僕にもありました。
Pythonで式True, True, True == (True, True, True)
がどう評価されるのか、頭の中で考えてみてほしい。
そして、お手元のPythonインタプリタで True, True, True == (True, True, True)
を実行してほしい。
どうなるだろうか。
>>> True, True, True == (True, True, True) (True, True, False)
上記の結果は、あなたが予想していた結果だろうか。 それとも、全く違う結果だっただろうか。
なぜこうなるのか
ひとまず、なぜこうなるのか、Pythonの標準ライブラリの1つであるバイトコード逆アセンブラのdis
を使って、上記の式がどのようにバイトコードに変換されるのかを調べてみる。
>>> import dis >>> dis.dis("True, True, True == (True, True, True)") 1 0 LOAD_CONST 0 (True) 2 LOAD_CONST 0 (True) 4 LOAD_CONST 0 (True) 6 LOAD_CONST 1 ((True, True, True)) 8 COMPARE_OP 2 (==) 10 BUILD_TUPLE 3 12 RETURN_VALUE
dis
のドキュメントに沿ってこのコードがどのような処理が行われるのかを追ってみる。
- スタックに
True
をプッシュする[True]
- スタックに
True
をプッシュする[True, True]
- スタックに
True
をプッシュする[True, True, True]
- スタックに
(True, True, True)
をプッシュする[True, True, True, (True, True, True)]
- スタックの先頭2つを等価比較して、結果をプッシュする
True == (True, True, True)
の結果はFalse
である- 結果の
False
をスタックにプッシュする[True, True, False]
- スタックから3つ取り出してタプルを作ってスタックにプッシュする
[(True, True, False)]
- スタックの先頭を返り値として返す
(True, True, False)
上記からわかることは、タプル同士の比較ではなく、左辺の一番右のTrue
と右辺のタプルの比較が行われていることがわかる。
これは、そのようにパースされたからだと思われる。
実際、 6. 式 (expression) — Python 3.8.2 ドキュメント の「演算子の優先順位」を調べると、
Binding or parenthesized expression
よりも==
の優先順序が上なので、タプル同士の比較ではなく上記のような動きになることがわかる。
どうすれば防げるのか
防ぐ方法の1つはmypyによるチェックである。
例えば、 hoge.py
として、
b = True, True, True == (True, True, True) print(b)
であるとき、
$ mypy --strict hoge.py
とすると、
hoge.py:1: error: Non-overlapping equality check (left operand type: "Literal[True]", right operand type: "Tuple[bool, bool, bool]") Found 1 error in 1 file (checked 1 source file)
という結果が返ってくる。
--strict
で指定される引数のうち、 --strict-equality
が利いていることがわかる。