52050


python 3 の typing を用いて、以下のようなロジックを書きました。

[code]

    from typing import Union
    from decimal import Decimal
    
    Number = Union[int, float, Decimal]
    
    
    def multiply_numbers(a: Number, b: Number) -> Number:
        if type(a) is float or type(b) is float:
            return float(a) * float(b)
        return a * b
    
[/code]

しかしこれは、一番最後の行で、以下のエラーが発生します。

[code]

    test.py:10: error: Unsupported operand types for * ("float" and "Decimal")
    test.py:10: error: Unsupported operand types for * ("Decimal" and "float")
    
[/code]

これは、その上で `a` と `b` が float でないことを確認しているので、間違いなく false positive だと思っています。

### 質問

  * python の mypy における Union の、選択肢を場合分けで潰していく処理は、どのように記述するのが正しいのでしょうか？


CC BY-SA 4.0
2019-01-17T02:38:21.403
52046
2019-01-17T08:00:53.617
754
question
python python3 mypy
mypy で Union の可能性を if で潰したが、エラーが消えない


Union 型を具体的な型に場合分けする際には [`isinstance()`
を使ってください](https://mypy.readthedocs.io/en/latest/kinds_of_types.html#union-
types)。下のように書くと mypy の静的型検査を通ります。

[code]

    def multiply_numbers(a: Number, b: Number) -> Number:
        if isinstance(a, float) or isinstance(b, float):
            return float(a) * float(b)
        return a * b
    
[/code]

また、質問文のプログラムは false positive ではありません。[`type()`
を使った比較はクラスの継承関係を無視する](https://stackoverflow.com/q/1549801/5989200)ので、たとえば
`float` を継承したクラス `MyFloat` を考えると不都合が生じます。下のコードを考えてください。

[code]

    class MyFloat(float):
        pass
    
    if __name__ == '__main__':
        x = MyFloat(3.14)
        print(multiply_numbers(x, 42))
    
[/code]

`type(x)` は `<class '__main__.MyFloat'>` であり `<class 'float'>` ではありません。しかし
`MyFloat` は `float` の子クラスなので、`multiply_numbers()` の引数に `x` を渡すことができます。すると
`type(x)` は `float` でないのに中身は `float` という状況が発生し、条件式 `type(a) is float`
では取りこぼしが発生します。


CC BY-SA 4.0
2019-01-17T07:19:51.503
52050
2019-01-17T08:00:53.617
2019-01-17T08:00:53.617
19110
19110
52046
answer