LoginSignup
137
132

More than 3 years have passed since last update.

cython入門

Last updated at Posted at 2018-07-24

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
or
> 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
    sum = 0
    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
    sum = 0
    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
from numpy import get_include # cimport numpy を使うため

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
from numpy import get_include # cimport numpy を使うため

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/en-san/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ではなく、直にCで書くこともできます。(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)

137
132
2

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
137
132