wrapping a c++ library with cython
TRANSCRIPT
Wrapping a C++ library with Cython
Tokyo.SciPy #52013-01-26
1 / 39
概要
Cythonを用いたC/C++ライブラリの Pythonラッパー作成Python/C API
の初歩について説明する.
2 / 39
Outline
...1 Introduction
...2 Cython
...3 Python/C API
...4 Summary
3 / 39
1. Introduction
4 / 39
動機
PythonとC/C++の両方の長所を活かしたい
C/C++ 実行速度の速さデータ構造,数値計算ライブラリが揃っている
→計算量が支配的な箇所に使うPython 読み書きしやすさ,柔軟性,拡張性,ポータビリティ
利便性の高いライブラリが揃っている
→その他のすべての箇所に使う
データ収集,前処理コマンドライン, GUIアプリ,ウェブアプリ設定ファイル,ジョブ管理,ログ管理テスト,可視化,ドキュメンテーション, · · ·
⇒ PythonからC/C++を呼び出せればよい5 / 39
6 / 39
Python/C API
Python/C APIを用いて Pythonの拡張モジュールを作成することにより以下が可能となる.
新しいオブジェクトの追加
C/C++の呼び出し
http://docs.python.jp/2/c-api/index.html
http://docs.python.jp/2/extending/extending.html
7 / 39
say.c
#include <Python.h>
static PyObject* say_hello(PyObject* self, PyObject* args) {
const char* name;
if (!PyArg_ParseTuple(args, "s", &name))
return NULL;
printf("Hello %s!\n", name);
Py_RETURN_NONE;
}
...
setup.py
setup(name="say", ext_modules=[
Extension("say", ["say.c"])
])
8 / 39
$ python setup.py build_ext --inplace
$ python
>>> import say
Pythonの内部構造についての知識が必要メモリ管理,例外処理などが面倒コード量が多い
⇒拡張モジュールを作成するためのツールを使う
9 / 39
拡張モジュールを作成するためのツール
C/C++による拡張モジュールを作成するためのツールとしてPyrexCythonSWIGSIPBoost.Python
などがある.
以下ではCythonを用いてC/C++ライブラリの Pythonラッパーを作成する方法について説明する.
10 / 39
2. Cython
11 / 39
Cython
Pythonの拡張モジュールを作成するための言語Python +型宣言を基本とした言語仕様CPython 2.4-3.x, Windows/Mac/LinuxApache Licenselxml, Numpy, Scipy, Sage, mpi4py, petsc4py, · · ·http://cython.org/
12 / 39
拡張モジュールの作成方法...1 pyxファイルを作成する...2 Cythonを用いて pyxファイルを c/cppファイルに変換する...3 c/cppファイルをコンパイルする
生成された soファイルは Pythonから直接インポートできる.
setup(ext_modules=cythonize("foo.pyx"))
$ python setup.py build_ext --inplace
$ python
>>> import foo
13 / 39
Cythonでは Pythonのソースコードが (ほぼ)そのまま使える
さらに以下のような言語仕様が加えられている.型宣言
C/C++の読み込み条件付きコンパイル
コンパイラディレクティブ
etc.
14 / 39
型宣言
cdef int i, j[10]
cdef float f, *g
cdef struct Rectangle:
float width
float height
cdef enum State:
open = 1
closed = 2
cdef object pyobj
ctypedef unsigned long uint64_t
from libc.stdint cimport int64_t
15 / 39
型変換
基本的な数値型と文字列型については, PythonオブジェクトとC変数が自動変換される.
cdef bytes py_byte_string
cdef unicode py_unicode_string
cdef char* c_string
py_byte_string = <bytes> c_string
py_byte_string = c_string
py_byte_string = c_string[:length]
c_string = py_byte_string
py_unicode_string = py_byte_string.decode("utf-8")
py_byte_string = py_unicde_string.encode("utf-8")
16 / 39
関数定義defにより定義
引数,返り値ともに PythonオブジェクトPythonから呼び出せる
cdefにより定義引数,返り値ともにC変数 (Pythonオブジェクトも含む)Pythonから呼び出せない
def integrate(double a, double b, int N):
# 引数, 返り値は自動的に型変換されるcdef int i
cdef double s, dx
s = 0; dx = (b - a) / N
for i in range(N):
s += f(a + i * dx)
return s * dx
cdef float f(double x) except *:
return 1 / x
17 / 39
拡張型cdef class Interval:
cdef public float x0, x1
def __init__(self, x0, x1):
self.x0 = x0; self.x1 = x1
@property
def length(self):
return self.x1 - self.x0
def widen(Interval i not None, r):
i.x0 *= r; i.x1 *= r
ビルトイン型,拡張型を継承できる. 多重継承はできない.アトリビュートには public, readonlyを指定できる拡張型の値は Noneを取りうる拡張型の引数には not Noneを指定できる<MyClass?>は型チェック付きキャスト
18 / 39
拡張型の初期化
cinit Cレベルの初期化を行う.必ず一度だけ呼び出される.この時点では Pythonオブジェクトとして不完全.
init cinit 以外の初期化を行う.複数回呼ばれる/1回も呼ばれない場合もある.
dealloc Cレベルの破棄処理.この時点では Pythonオブジェクトとして不完全.
基底型の cinit が先に呼び出される.コンストラクタに渡した引数は cinit , init の両方に渡される.
19 / 39
C言語とのシンタックスの違い
constは使えない
ヌルポインタは NULLにより表す
p->aの代わりに p.aを使う
&xの代わりに cython.address(x)を使う
*pの代わりに p[0] or cython.operator.dereference(p)を使う
20 / 39
Cの読み込み
CythonはCのヘッダファイルを読まないため,以下のような宣言が必要となる.
cdef extern from "math.h":
double sin(double)
double M_PI
def py_sin(d):
# Pythonから呼び出し可能return sin(M_PI / 180.0 * d)
21 / 39
C++の読み込み
名前空間
クラス
テンプレート
演算子オーバーロード
ポリモーフィズム
などに対応している.
C++の例外は対応する Pythonの例外に翻訳される.
22 / 39
STLコンテナ
STLコンテナは libcpp以下から cimportするだけで使える対応する Python組み込み型があれば自動変換される
from libcpp.string cimport string
cdef string cpp_string
cdef bytes py_byte_string
cpp_string = <string> py_byte_string
cpp_string = py_byte_string
py_byte_string = cpp_string
23 / 39
C++クラスの宣言pair.pyx
cdef extern from "<utility>" namespace "std":
cdef cppclass pair[T, U]:
T first
U second
pair() nogil except +
pair(pair&) nogil except +
pair(T&, U&) nogil except +
bint operator==(pair&, pair&) nogil
bint operator!=(pair&, pair&) nogil
...
cdef pair[int, char*] *p = new pair[int, char*](1, "One")
setup.py
setup(ext_modules=cythonize("pair.pyx", language="c++"))
24 / 39
Pythonラッパークラスcppclassを Pythonから呼び出すにはラッパークラスが必要.
cdef class PyPair:
cdef pair[int, char*] *thisptr
def __cinit__(self, *args, **kw):
self.thisptr = new pair[int, char*]()
def __init__(self, int i, char* s):
self.thisptr.first = i
self.thisptr.second = s
def __dealloc__(self):
del self.thisptr
@property
def first(self):
return self.thisptr.first
@property
def second(self):
return self.thisptr.second
25 / 39
デバッガ
$ python-dbg setup.py build_ext --pyrex-gdb --inplace
$ cygdb
(gdb)
使い方はGDBとほぼ同じ.ブレークポイントの設定
スタックのインスペクション
ステップ実行
etc.
26 / 39
typednessのアノテーションCythonおよび C/C++コードを typednessにより色分けしたHTMLファイルを生成する.
$ cython foo.pyx -a
http://docs.cython.org/src/quickstart/cythonize.html
27 / 39
プロファイリング
次のディレクティブによりプロファイリングが有効になる.
# cython: profile=True
使用方法は cProfileを使った Pythonのプロファイリングと同じ.
cProfile.runctx("extmod.func()",
globals(), locals(), "Profile.prof")
s = pstats.Stats("Profile.prof")
28 / 39
その他の機能
Numpyとの連携Sage Notebookとの連携コンパイラディレクティブ
条件付きコンパイル
融合型
型つきメモリビュー
並列化
GIL制御etc.
詳しくは http://docs.cython.org/
29 / 39
3. Python/C API
30 / 39
Cythonにより生成されたC/C++ファイルを読むには Python/C APIの知識が必要となる. 以下ではその初歩について説明する.
詳しくは http://docs.python.jp/2/extending/index.html
31 / 39
すべてのデータはオブジェクト
#define PyObject_HEAD
_PyObject_HEAD_EXTRA /* デバグ用*/ \Py_ssize_t ob_refcnt; /* 参照カウンタ */ \struct _typeobject *ob_type; /* 型オブジェクト */
typedef struct _object {
PyObject_HEAD
} PyObject;
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
PyInt FromLongなどで Pythonオブジェクトを構築PyInt Checkなどで型チェックPyInt AsLongなどでC変数を取得
32 / 39
33 / 39
Pythonから呼び出す関数の引数,返り値は Pythonオブジェクト
static PyObject *
func(PyObject *self, PyObject *args) {
const char *s;
if (!PyArg_ParseTuple(args, "s", &s))
return NULL;
...
}
typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
PyErr *で例外処理
if (PyErr_Occurred()) {
if (/* StopIterationだったら */) PyErr_Clear();else { goto __pyx_error; }
}
34 / 39
PyObject Call*で Pythonオブジェクトの呼び出し
PyObject* PyObject_Call(PyObject *callable,
PyObject *args, PyObject *kw)
args, kwのチェック,再帰の管理などが行われる
ガベージコレクションは参照カウント法
参照カウントの振る舞い “参照の所有権”により理解されるPy INCREF, Py DECREFで参照カウンタを増減参照カウンタが 0になったオブジェクトは破棄される
35 / 39
4. Summary
36 / 39
まとめ
Cythonを使ってC/C++ライブラリの Pythonラッパーを“手軽に”作ることが出来る拡張ライブラリの仕組みを把握するにはPython/C APIの知識が必要
CythonはC/C++と Pythonの両方の長所を活かすための
橋渡しとしての役割を果たす
37 / 39
References
[1] http://docs.python.jp
[2] http://docs.cython.org/
[3] Cythonユーザメーリングリスト
[4] D. S. Seljebotn, Fast numerical computations with Cython,Proceedings of the 8th Python in Science Conference, 2009.
38 / 39
fin.
Revision: 9176288 (2013-01-25)
39 / 39