[python] スカラーの判定方法

入力引数がスカラーかどうかを簡単に判定したいとき，matlabだと[isscalar](https://jp.mathworks.com/help/matlab/ref/isscalar.html)という関数があるが，pythonの場合，どうするのがよさそうかわからなかったので調べた。

# バージョン情報
こういった話はバージョン依存性があるので，本記事作成時のバージョンを残しておく。

- python 3.7.3
- numpy 1.17.0

# 方法1: Numberクラスを継承しているかどうかで判定
- [numbers --- 数の抽象基底クラス](https://docs.python.org/ja/3/library/numbers.html)

を読むと，こんなことが書かれている。

> class numbers.Number
> 　数の階層の根。引数 x が、種類は何であれ、数であるということだけチェックしたい場合、isinstance(x, Number) が使えます。


コードにすると，こんな感じ。シンプルでよい。

```py
import numbers

def is_scalar(x):
    return isinstance(x, numbers.Number)
```

# 方法2 : numpyの判定関数を使う
調べてみると，numpyに`numpy.isscalar`という関数があった。仕様は[このあたり](https://docs.scipy.org/doc/numpy/reference/generated/numpy.isscalar.html)が見やすい。

ソースコード上の定義箇所は[ここ](https://github.com/numpy/numpy/blob/v1.17.0/numpy/core/numeric.py#L1783)だった。コードを見る限り，方法1よりもスカラーと判定する範囲が確実に広い。関数コメントを見る限り，strとかもスカラーとする仕様になっている（個人的にはちょっと違和感がある。strが係数になるベクトル空間とか作れるのか？）

# スカラー判定の範囲
よく使いそうなオブジェクトで試したので，一応載せておく。試した中では，`str`と`bytes`が方法1だとFalse，方法2(numpy.isscalar)だとTrueになり，それ以外は判定結果が一致した。実装時はこのあたりの違いに気を付けたほうがよさそう。

```scalar_test.py
import numpy
import numbers
import decimal

def is_scalar(x):
    return isinstance(x, numbers.Number)

def check(input_var):
    print("=========================")
    print(input_var)
    print(type(input_var))
    print("is_sclar_1: " + str(is_scalar(input_var)))
    print("is_sclar_2: " + str(numpy.isscalar(input_var)))

if __name__ == "__main__":
    # 方法1，2ともにスカラーと判定
    var_int = 1
    var_bool = True
    var_float = 1.0
    var_complex = 1 + 1j
    var_npint32 = numpy.int32(1)
    var_decimal = decimal.Decimal('12.345')

    # 方法1はスカラー，方法2は非スカラーと判定
    var_bytes_1 = bytes(1)
    var_bytes_hoge = b'hoge'
    var_str1 = "1"
    var_str4 = "hoge"

    # 方法1，2ともに非スカラーと判定
    var_nan = None
    var_nparray = numpy.array(1)
    var_list = [1]
    var_taple = (1,2)  #(1)ならtapleにならないのでスカラーと判定される
    var_dict = {"a": 1}
    var_set = {1}

    # 検証
    check(var_int)
    check(var_bool)
    check(var_float)
    check(var_complex)
    check(var_npint32)
    check(var_decimal)

    check(var_bytes_1)
    check(var_bytes_hoge)
    check(var_str1)
    check(var_str4)

    check(var_nan)
    check(var_nparray)
    check(var_list)
    check(var_taple)
    check(var_dict)
    check(var_set)
```

出力結果

```sh
=========================
1
<class 'int'>
is_sclar_1: True
is_sclar_2: True
=========================
True
<class 'bool'>
is_sclar_1: True
is_sclar_2: True
=========================
1.0
<class 'float'>
is_sclar_1: True
is_sclar_2: True
=========================
(1+1j)
<class 'complex'>
is_sclar_1: True
is_sclar_2: True
=========================
1
<class 'numpy.int32'>
is_sclar_1: True
is_sclar_2: True
=========================
12.345
<class 'decimal.Decimal'>
is_sclar_1: True
is_sclar_2: True
=========================
b'\x00'
<class 'bytes'>
is_sclar_1: False
is_sclar_2: True
=========================
b'hoge'
<class 'bytes'>
is_sclar_1: False
is_sclar_2: True
=========================
1
<class 'str'>
is_sclar_1: False
is_sclar_2: True
=========================
hoge
<class 'str'>
is_sclar_1: False
is_sclar_2: True
=========================
None
<class 'NoneType'>
is_sclar_1: False
is_sclar_2: False
=========================
1
<class 'numpy.ndarray'>
is_sclar_1: False
is_sclar_2: False
=========================
[1]
<class 'list'>
is_sclar_1: False
is_sclar_2: False
=========================
(1, 2)
<class 'tuple'>
is_sclar_1: False
is_sclar_2: False
=========================
{'a': 1}
<class 'dict'>
is_sclar_1: False
is_sclar_2: False
=========================
{1}
<class 'set'>
is_sclar_1: False
is_sclar_2: False
```

# 参考URL
- [stackoverflow : What is a “scalar” in numpy?](https://stackoverflow.com/questions/21968643/what-is-a-scalar-in-numpy)
- [stackoverflow : Identifying numeric and array types in numpy](https://stackoverflow.com/questions/500328/identifying-numeric-and-array-types-in-numpy)