Cythonの型の取り扱いが面倒だったので気を付けた点をまとめた

Cythonの使い方についての解説はあるが、型についての記事が少ないように感じたのでまとめる。
CythonはほぼPythonと変わらない書き方でCとPythonをインターフェースを作れる、更にPythonの高速化が望める言語である。しかし、CとPythonの型が混在することで型エラーが頻発する。私はCythonの難しさは**型のコントロール**であると思った。

#静的型付けと動的型付けの融合
CythonはPythonのような動的型付けもできるし、Cのような静的な型付けができるという言語だ。Pythonで動的型付けと静的型付けの強みを享受できる便利な機能であるが、CythonではPythonの型だけでなくCの型が混在するため、注意しなければならないポイントが多い。
Cythonを使用する時に、肝に銘じておいて欲しいのは**Cythonは型を明示的に型付けしていなければ、それはPythonの型として扱われる。（実体がPythonレベルの型でない場合、Cレベルの型にキャストされる）**ということである。
ちなみに、Cythonでは`vector<int>`を`vector[int]`と書く。

```python
    cdef vector[int] vec1 # Cレベルの型
```

Cythonでは`cdef`を使うことで変数をCレベルの型で変数宣言できる。なお、この`cdef`はdocstringなどのコメントを除いて、必ず関数の最初に書かなければいけないという制約がある。つまり以下のように`if文`で分岐によって`cdef`する型を変えることはできない。


```python
#このような書き方はできない
cdef func0(type):
    if type == "double":
        cdef double x = 10
        return x
    elif type == "int":
        cdef double y = 10
        return y
```
Cythonでは返り値も明示的に指定できる。この型指定はPythonの型を返り値とする場合任意だが、Cレベルの型を返り値とする場合は型を指定しなければならない。もし、指定しなかった場合、Pythonレベルの型に変換される。

```python
cdef func1(type):
     cdef vector[double] vec
     return vec #各要素がfloatのリストに変換される

cdef vector[double] func2(type):#返り値の型を指定
     cdef vector[double] vec
     return vec #vector[double]のまま返る
```

CythonにはPythonレベルの型とCレベルの型を暗黙のうちに型変換してくれる機能がある。例えばでvector[double]を返り値の型として指定しなくても、自動的にCythonが`vector<double>`の`vector`を`list`に、`double`を`float`に変換する。

返り値としたい型がユーザー定義型やCythonで扱えないCのライブラリ固有の型である場合、Pythonレベルへの変換が定義されていないためコンパイルエラーが起きる。
以下の例では`cdef`関数内からMyClassというCで定義した型を返している。このMyClass型はPythonの型への変換方法が定義されていないので、以下の例ではエラーが起きる。

```python
cdef func3():
    cdef MyClass x = MyClass()
    return x # エラー MyClass型はPythonの型に変換できない　

func3()
```
このため、Cレベルの型の値を返したい場合は、以下のように返り値の型を指定する必要がある。

```python
cdef MyClass func3():
    cdef MyClass x = MyClass()
    return x # OK 返り値はMyClassである

func3()
```
これはMyClassを要素とする`vector`でも同様である。

#型キャストとオーバーロード
CythonはPythonを高速化できるだけでなく、Cの関数や型をラップできるのが大きな魅力だ。自分もラップをしようとしたがCでオーバーロードしている関数をラップしたい場合にかなりてこずった。


```python
cdef extern from "rect.h":
    int area(int x,int y)
    double area(double x,double y)
```
例えば上のような関数があるとしよう。これをxの型によって、呼び出す関数を変えたいので以下のように書く。するとエラーが起きる。

```python
#エラー　適切なメソッドを呼び出せない
def py_area (x,y):
   if type(x) == int:
        return area(x,y) 
   elif type(x) == float:
        return area(x,y) 
```
このような書き方をしたい場合、実引数として渡す値を全て適切な型に明示的キャストしてから渡す必要がある。つまり型キャストをその場で明示的な型付けを行うのに使うということになる。

```python
#OK
def py_area (x,y):
    if type(x) == int:
        return area(<int>x,<int>y) 
    elif type(x) == float:
        return area(<double>x,<double>y) 
```

しかし、仮引数が参照渡しで宣言されているときは仮引数をその場でキャストしながら渡すことができない。この時、予め`cdef`で型宣言しておくことで明示的な型指定しながら渡すことができる。

例えば関数が下のようになっているなら

```python
cdef extern from "rect.h":
    int area(int& x,int& y)
    double area(double& x,double& y)
```
今までの書き方を下のような書き方にする必要がある。

```python
def py_area (x,y):
    cdef:
        int x1
        int y1
        double x2
        double y2
    if type(x) == int:
       x1 = x
       y1 = y
       return area(x1,y1) 
    elif type(x) == float:
       x2 = x
       y2 = y
       return area(x2,y2) 
```

すると、参照型で仮引数が定義されていてもエラーにならない。

#Fused type
Cythonには**Fused type**(融合型)という機能がある。これはCythonで実質的にテンプレート型を使う機能である。返り値や引数に複数の型がありうる場合に使える。

```python
#任意の型を羅列
ctypedef fused my_type:
    hoge
    foo
    bar 
```
`Fused type`は上記のように型を羅列することで`my_type`は`hoge`、`foo`、`bar`のどの型としても扱われるようになる。
これを使うことで、多次元リストのCとPython間の型変換を以下のように実現できる。`PyClass`は`MyClass`のPython上での型とする。

```python
ctypedef fused T:
    MyClass
    vector[MyClass]

cdef vector_to_list (T x):
 if T == vector[MyClass]:
    return [vector_to_list(i) for i in range(<vector[MyClass]>x.size())]
 else :  
    return PyClass(x)
```

Fused typeは全ての型である可能性を考慮して構文解析する。例えば上の例ならCythonのxは`vector[MyClass]`だけでなく、`MyClass`の可能性があると見なしてしまう。キャストによって明示的な型指定をせず下のように書くとMyClassには`size()`がないのでエラーを起こす。

```python
ctypedef fused T:
    MyClass
    vector[MyClass]

cdef vector_to_list (T x):
 if T == vector[MyClass]:
    return [vector_to_list(i) for i in range(x.size())] # (1)
 else :  
    return PyClass(x)
```
この例では、(1)の行を`<vector[MyClass]>`でキャストしていないため型`T`がMyClassの可能性があると見なされる。そして、MyClassにはsize()が定義されていないというエラーが起きる。


#まとめ
CythonはCとPythonが融合している言語故に、二つの特性を活用できるが面倒ごとが多く感じた。
とりあえず、Cythonの型に困ったら、`cdef`による静的型付けやキャストをうまく使って切り抜けよう。

#参考文献
- Kurt W. Smith　著、中田 秀基　監訳、長尾 高弘　訳 「Cython - Cとの融合によるPythonの高速化」2015
- [Welcome to Cython’s Documentation — Cython 3.0a0 documentation](https://cython.readthedocs.io/en/latest/)