mypy 比較演算子の型チェックが正しく機能しない
解決したいこと
mypy で、
__lt__
特殊メソッド (<
) に bool
以外の型を返却させ、
@total_ordering
を利用して >
を 自動で補完したとき、
gt:不正な型 = obj > other
としても、不正な型を検知できない場合がありました。
これはバグでしょうか?
環境
python 3.13.1, mypy 1.14.0
発生している問題
次のような test.py
を用意します。
from functools import total_ordering
@total_ordering
class 左型:
def __lt__(self, other:object)->int: # 意地悪で bool 以外の型を返却
print("左 < 右 が 0 を返しました。")
return 0
def __eq__(self, other:object)->bool:
print("左 == 右 が True を返しました。")
return True
class 右型: pass
class 全く無関係の型: pass
左:左型 = 左型()
右:右型 = 右型()
大なり:全く無関係の型 = 左 > 右
print("左 > 右 ==", 大なり, type(大なり))
これを python で実行すると、次のような結果を得ます。
左 < 右 が 0 を返しました。
左 == 右 が True を返しました。
左 > 右 == False <class 'bool'>
つまるところ、 左 > 右
の結果は bool
型になりますので、
大なり:全く無関係の型 = 左 > 右
は型チェックでエラーにならなければいけません。
しかし、実際に mypy test.py
すると、
Success: no issues found in 1 source file
となってしまい、全く無関係の型を見逃してしまいました。
参考情報
test.py
に対し、 左型.__lt__
が bool
型を返却するように改修した test2.py
では、mypy はちゃんとエラーを返しました。
具体的には、
from functools import total_ordering
@total_ordering
class 左型:
def __lt__(self, other:object)->bool: # bool に改修
print("左 < 右 が False を返しました。")
return False
def __eq__(self, other:object)->bool:
print("左 == 右 が True を返しました。")
return True
class 右型: pass
class 全く無関係の型: pass
左:左型 = 左型()
右:右型 = 右型()
大なり:全く無関係の型 = 左 > 右
print("左 > 右 ==", 大なり, type(大なり))
に対して mypy test2.py
を実行すると
test2.py:20: error: Incompatible types in assignment (expression has type "bool", variable has type "全く無関係の型") [assignment]
Found 1 error in 1 file (checked 1 source file)
となり、期待通りのエラーを出してくれました。
まとめ
mypyでは、
-
bool
以外を返す比較演算子を用意して、@total_ordering
すると、型チェックが機能しない場合がある - 比較演算子が必ず
bool
を返す場合に@total_ordering
すると、型チェックは機能する
という挙動が見られました。
この挙動について、何かご存じの方がいらっしゃいましたら、ご知見共有いただけますと幸いです。
自分で試したこと
ChatGPT-4o に上記の質問を投げました。
その回答は
https://chatgpt.com/share/6774ea0a-b8e8-8013-b580-e70d99fe0276
から閲覧できます。
回答内容を一部引用すると、
背景
@total_ordering
の仕組み:
@total_ordering
は、少なくとも__lt__
と__eq__
が実装されていれば、他の比較演算子(__le__
,__gt__
,__ge__
)を自動で補完します。- 補完されたメソッドは
__lt__
と__eq__
の結果を使いますが、型アノテーションは補完されたメソッドには付与されません。mypy
はこの補完されたメソッドに型を推測する際、__lt__
や__eq__
の型を元にします。mypy
の型推論の限界:
__lt__
がbool
を返す場合、mypy
は補完された比較演算子もbool
を返すと推論します。- 一方で、
__lt__
がbool
以外 を返す場合、補完された比較演算子の型が正しく推論されず、静的型チェックが機能しなくなることがあります。
とのことでした。
補完された比較演算子の型が正しく推論できないことが分かっているなら、その旨のエラーを出す仕様にすればよいだけではないでしょうか。なぜ「推論できないエラー」を投げず、型チェックの成功をでっちあげる仕様になっているのか疑問です。