hiratatomotaka
@hiratatomotaka

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

mypy 比較演算子の型チェックが正しく機能しない

解決したいこと

mypy で、
__lt__ 特殊メソッド (<) に bool 以外の型を返却させ、
@total_ordering を利用して > を 自動で補完したとき、
gt:不正な型 = obj > other としても、不正な型を検知できない場合がありました。

これはバグでしょうか?

環境

python 3.13.1, mypy 1.14.0

発生している問題

次のような test.py を用意します。

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 はちゃんとエラーを返しました。

具体的には、

test2.py
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
から閲覧できます。

回答内容を一部引用すると、

背景

  1. @total_ordering の仕組み:
    • @total_ordering は、少なくとも __lt____eq__ が実装されていれば、他の比較演算子(__le__, __gt__, __ge__)を自動で補完します。
    • 補完されたメソッドは __lt____eq__ の結果を使いますが、型アノテーションは補完されたメソッドには付与されません
    • mypy はこの補完されたメソッドに型を推測する際、__lt____eq__ の型を元にします。
  2. mypy の型推論の限界:
    • __lt__bool を返す場合、mypy は補完された比較演算子も bool を返すと推論します。
    • 一方で、__lt__bool 以外 を返す場合、補完された比較演算子の型が正しく推論されず、静的型チェックが機能しなくなることがあります。

とのことでした。

補完された比較演算子の型が正しく推論できないことが分かっているなら、その旨のエラーを出す仕様にすればよいだけではないでしょうか。なぜ「推論できないエラー」を投げず、型チェックの成功をでっちあげる仕様になっているのか疑問です。

0

1Answer

自己解決です。
reveal_type(左 > 右) すると、Any が返ってきました。仕様のようですね。
(int) and (bool)reveal_type(int(input()) and bool(input())) をやったところ、 Literal[0] | bool になるようでした。

Literal[0] | bool とならず Any で妥協する理由がわかりませんが、
一応解決はしました。

0Like

Your answer might help someone💌