Open1

ユニオン型の変数をタプルにしてパターンマッチさせるとmypyではエラーになる件

imamurayimamuray

以下のサンプルコードのようなものを書いたら mypy チェックでエラーになった。mypy はエラーになるがコードとして実行は可能。

バージョン: python3.11.9, mypy 1.10.0
サンプルコード:

def check(left: int | None, right: int | None) -> str:
    match left, right:
        case None, None:
            return "Both None"
        case _, None:
            return "Right is None"
        case None, _:
            return "Left is None"
        case l, r if l == r:
            return "Equal"
        case l, r if l < r: # ここでエラー、 int と推論してくれない
            return "Right is higher than left"
        case _:
            return "Left is higher than right"

vscode拡張の mypy-type-checker のエディタ上で出たエラーメッセージ:

Unsupported operand types for < ("int" and "None") 
Unsupported operand types for > ("int" and "None") 
Both left and right operands are unionsMypy
Unsupported left operand type for < ("None") 

https://github.com/python/mypy/issues/15426#issuecomment-1589505882 によると mypy の仕様らしく、pyright ではこれはエラーにならないとのこと。

mypy と pyright では型の扱いの思想が違うらしい。
https://future-architect.github.io/articles/20220301a/

やりたいことのイメージを Scala で書くと以下(ChatGPT製)

def describe(a: Option[Int], b: Option[Int]): String = (a, b) match {
  case (None, None)       => "Nothing"
  case (Some(x), None)    => x.toString
  case (None, Some(y))    => y.toString
  case (Some(x), Some(y)) => s"$x and $y"
}

// 使用例
val result1 = describe(Some(3), None)      // "3"
val result2 = describe(None, Some(5))      // "5"
val result3 = describe(None, None)         // "Nothing"
val result4 = describe(Some(2), Some(4))   // "2 and 4"

println(result1)
println(result2)
println(result3)
println(result4)