8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-20

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]と書く。

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

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

#このような書き方はできない
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レベルの型に変換される。

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>vectorlistに、doublefloatに変換する。

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

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

func3()

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

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

func3()

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

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

cdef extern from "rect.h":
    int area(int x,int y)
    double area(double x,double y)

例えば上のような関数があるとしよう。これをxの型によって、呼び出す関数を変えたいので以下のように書く。するとエラーが起きる。

#エラー 適切なメソッドを呼び出せない
def py_area (x,y):
   if type(x) == int:
        return area(x,y) 
   elif type(x) == float:
        return area(x,y) 

このような書き方をしたい場合、実引数として渡す値を全て適切な型に明示的キャストしてから渡す必要がある。つまり型キャストをその場で明示的な型付けを行うのに使うということになる。

#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で型宣言しておくことで明示的な型指定しながら渡すことができる。

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

cdef extern from "rect.h":
    int area(int& x,int& y)
    double area(double& x,double& y)

今までの書き方を下のような書き方にする必要がある。

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で実質的にテンプレート型を使う機能である。返り値や引数に複数の型がありうる場合に使える。

#任意の型を羅列
ctypedef fused my_type:
    hoge
    foo
    bar 

Fused typeは上記のように型を羅列することでmy_typehogefoobarのどの型としても扱われるようになる。
これを使うことで、多次元リストのCとPython間の型変換を以下のように実現できる。PyClassMyClassの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()がないのでエラーを起こす。

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による静的型付けやキャストをうまく使って切り抜けよう。

#参考文献

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?