Python
Cython


cythonとは

pythonライクな言語でかけて、コンパイルすることでpythonから使えるライブラリが作成されます。

ほとんどpythonっぽい書き方なので、少しのC言語の知識があればすぐに使えると思います。

pythonに変数宣言を加えたような言語で、変数の型を指定できるので、pythonの型推定が入らなかったりで高速化されるみたいです。

また、コンパイルするときには、一度C言語のファイルに変換されてからコンパイルされます。

使うもの(基本):

- sample.pyx

- setup.py

(発展):

- sample.pxd (pyxファイルと同じ名前)

- csample.c (pyxファイルとは違う名前で)

- csample.h

できるもの:

- sample.~~~.so (コンパイルされた後のライブラリ)

- sample.c (ライブラリを作るための中間的な生成物)


cythonの導入方法

pipかcondaでインストール。


terminal

> pip install cython

> conda install cython

注意:ここではmacOSかLinux環境を想定してます。windowsでやるときはパスやらなんやらが少しめんどくさいはず。


基本


できること

cdefという言葉で、変数の型を固定できます。基本的にやることはこれだけ。


書き方

拡張子が.pyxのファイルを作成。(細かい書き方は少しずつ書いていきます)

関数はpythonのものをそのまま持ってきて、少し中身を変更します。



  • int型に固定したい変数があれば、cdef int xで固定します。

  • 関数をcdefで定義すると、pythonからは呼び出せなくなりますが、cythonの他の関数からだけ見えるので、その分早くなります。cpdefで関数を定義すると、cythonから見る用のものと、pythonから呼び出す用のものが両方作成されるはずです。


sample.pyx

import cython

import numpy as np
cimport numpy as cnp

# pythonから呼び出す関数
def func1(int n):
cdef:
int i, sum
list hoge
hoge = []
for i in range(n):
sum += i
hoge.append(i)
return sum, hoge

# pythonからは参照しない関数。cythonの中だけで使う場合
cdef func2(cnp.ndarray temp):
"""
tempが1次元のndarrayとき
"""

cdef:
int i, N, sum
N = len(temp)
for i in range(N):
sum += temp[i]
return sum

# pythonとcython両方から参照する場合で、cythonから参照するときに高速化したい場合
cpdef func3():
pass


cythonのモジュールを使うときはcimportを使います。使い方はpythonのモジュールとほとんど変わりませんが、オブジェクトの型が少し違います。

numpyをインポートするときはpythonのモジュールとcythonでのモジュールがバッティングしないように名前を変えます。


使い方

pyxファイルができたら、コンパイルしてpythonから使えるモジュールに変換します。

以下の、setup.pyを実行します。


setup.py

from distutils.core import setup, Extension

from Cython.Build import cythonize

ext = Extension("sample", sources=["sample.pyx"], include_dirs=['.', get_include()])
setup(name="sample", ext_modules=cythonize([ext]))



terminal

> python setup.py build_ext --inplace


すると、sample.~~~.soって感じのライブラリがカレントディレクトリにできます。

--inplaceオプションつけないと、違うとこにできます。

その後、


python

from sample import func1 #とか

import sample #とかすることで、 (ただし、func2は使えません。)

if __name__ == "__main__":
sample.func3(x)


のようにして使えます。

※ 他のコンパイル法もあります。


発展編


C言語で書いた外部関数をpythonで使う場合

C言語のコードをラップして使います。

拡張子.pxdのファイルで関数名を宣言します。

C言語でのヘッダファイルみたいなやつ。

注:C言語での関数名とpyxファイル内での関数名が被ってはいけない。


sample.pyx

import cython

def func(double x, double y):
cdef:
double z
z = cfunc(x, y)
return z
# zをわざわざおいたのは、変数宣言の例のためです。



sample.pxd

cdef extern from "csample.h":

double cfunc(double x, double y)

Cのファイルは


csample.c

#include<stdio.h>


double cfunc(double x, double y){
return x * y;
}

ヘッダファイルもいります。


csample.h

#ifndef _INCLUDE_SAMPLE_

#define _INCLUDE_SAMPLE_

double cfunc(double x, double y);

#endif


セットアップファイルも少し変更して、


setup.py

from distutils.core import setup, Extension

from Cython.Build import cythonize

ext = Extension("sample", sources=["sample.pyx", "csample.c"], include_dirs=['.', get_include()])
setup(name="sample", ext_modules=cythonize([ext]))


これを実行すれば、ライブラリができてpyxに書いた関数がインポートできるようになります。


numpyをCの1次元配列に渡す場合

numpy.ndarrayで作ったオブジェクトをCの配列にして、C言語での関数に渡す方法について書きます。

以下に例を軽く載せていますが、内容としては、ndarrayのポインタをCの関数に渡してその関数の中で計算させてます。コードに出てくるtemp.datatempのメモリ領域へのポインタに当たるものでこれを指定の型にキャストしてC言語の配列にする感じです。


from_ndarray.pyx

def func(cnp.ndarray[double, ndim=1, mode="c"] temp):

"""
1次元のdouble型のndarrayを引数にとる。
cfuncにCのポインタを渡して計算させる。
ちなみに、cfuncはvoid型の関数で行列を書き換える操作をする
"""

cdef:
int N
double *ctemp

N = len(temp) # 行列の長さをCの関数に入れるため
# ndarray tempをdouble型のpointerにする。渡しているのはtemp[0]のアドレス
ctemp = <double*> temp.data
# test.cのなかのcfuncを呼び出す。
cfunc(N, ctemp)


詳しいソースコードはここ( https://github.com/Daisuke323/cython_example )。

結果はどうなるかというと,


python

>>> import from_ndarray

>>> import numpy as np
>>> temp = np.zeros(10, dtype=np.float64)
>>> temp
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
# ラップした関数funcを呼び出す。
>>> from_ndarray.func(temp)
>>> temp
array([ 0., 2., 4., 6., 8., 10., 12., 14., 16., 18.])

func()関数によってndarraytempが書き換えられていることがわかりますね。


追記

公式のwikiの方が正確です。(https://github.com/cython/cython/wiki/tutorials-NumpyPointerToC)


C言語の1次元配列をnumpyに渡す場合

あとで書きます。


具体例


その他


  • C言語で書いた関数を使う

  • pythonの一部をC言語でかく

なら、cythonではなく、直にかくこともできます。(Python/C API)

ですが、cythonの方が楽だと思います。


最後に

cythonを使う楽なところはpythonのように返り値が複数取れたり、楽に変数、行列を初期化できたり、簡単にファイルを読み込めたりすることだと思います。C言語で書こうとすると色々煩わしい部分があるので、そこを簡単に克服できるのはよいですね。


参考文献

Cython ―Cとの融合によるPythonの高速化 (オライリージャパン)

Cython ドキュメント (http://omake.accense.com/static/doc-ja/cython/index.html#)

numpy.ndarray.ctypes (https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.ctypes.html)