Wrapping a C++ library with Cython



            Tokyo.SciPy #5
              2013-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++による拡張モジュールを作成するためのツールとして
  Pyrex
  Cython
  SWIG
  SIP
  Boost.Python
などがある.

以下では Cython を用いて C/C++ライブラリの Python ラッパーを
作成する方法について説明する.



                                      10 / 39
2. Cython




            11 / 39
Cython



   Python の拡張モジュールを作成するための言語
   Python + 型宣言を基本とした言語仕様
   CPython 2.4-3.x, Windows/Mac/Linux
   Apache License
   lxml, 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

Wrapping a C++ library with Cython

  • 1.
    Wrapping a C++library with Cython Tokyo.SciPy #5 2013-01-26 1 / 39
  • 2.
    概要 Cythonを用いた C/C++ライブラリの Python ラッパー作成 Python/C API の初歩について説明する. 2 / 39
  • 3.
    Outline 1. Introduction 2. Cython 3. Python/C API 4. Summary 3 / 39
  • 4.
  • 5.
    動機 Python と C/C++の両方の長所を活かしたい C/C++ 実行速度の速さ データ構造, 数値計算ライブラリが揃っている → 計算量が支配的な箇所に使う Python 読み書きしやすさ, 柔軟性, 拡張性, ポータビリティ 利便性の高いライブラリが揃っている → その他のすべての箇所に使う データ収集, 前処理 コマンドライン, GUI アプリ, ウェブアプリ 設定ファイル, ジョブ管理, ログ管理 テスト, 可視化, ドキュメンテーション, · · · ⇒ Python から C/C++を呼び出せればよい 5 / 39
  • 6.
  • 7.
    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
  • 8.
    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
  • 9.
    $ python setup.pybuild_ext --inplace $ python >>> import say Python の内部構造についての知識が必要 メモリ管理, 例外処理などが面倒 コード量が多い ⇒ 拡張モジュールを作成するためのツールを使う 9 / 39
  • 10.
    拡張モジュールを作成するためのツール C/C++による拡張モジュールを作成するためのツールとして Pyrex Cython SWIG SIP Boost.Python などがある. 以下では Cython を用いて C/C++ライブラリの Python ラッパーを 作成する方法について説明する. 10 / 39
  • 11.
    2. Cython 11 / 39
  • 12.
    Cython Python の拡張モジュールを作成するための言語 Python + 型宣言を基本とした言語仕様 CPython 2.4-3.x, Windows/Mac/Linux Apache License lxml, Numpy, Scipy, Sage, mpi4py, petsc4py, · · · http://cython.org/ 12 / 39
  • 13.
    拡張モジュールの作成方法 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
  • 14.
    Cython では Pythonのソースコードが (ほぼ) そのまま使える さらに以下のような言語仕様が加えられている. 型宣言 C/C++の読み込み 条件付きコンパイル コンパイラディレクティブ etc. 14 / 39
  • 15.
    型宣言 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
  • 16.
    型変換 基本的な数値型と文字列型については, 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
  • 17.
    関数定義 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
  • 18.
    拡張型 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
  • 19.
    拡張型の初期化 cinit C レベルの初期化を行う. 必ず一度だけ呼び出される. この時点では Python オブジェクトとして不完全. init cinit 以外の初期化を行う. 複数回呼ばれる/1 回も呼ばれない場合もある. dealloc C レベルの破棄処理. この時点では Python オブジェクトとして不完全. 基底型の cinit が先に呼び出される. コンストラクタに渡した引数は cinit , init の両方に渡さ れる. 19 / 39
  • 20.
    C 言語とのシンタックスの違い constは使えない ヌルポインタは NULL により表す p->a の代わりに p.a を使う &x の代わりに cython.address(x) を使う *p の代わりに p[0] or cython.operator.dereference(p) を使う 20 / 39
  • 21.
    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
  • 22.
    C++の読み込み 名前空間 クラス テンプレート 演算子オーバーロード ポリモーフィズム などに対応している. C++の例外は対応する Python の例外に翻訳される. 22 / 39
  • 23.
    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
  • 24.
    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
  • 25.
    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
  • 26.
    デバッガ $ python-dbgsetup.py build_ext --pyrex-gdb --inplace $ cygdb (gdb) 使い方は GDB とほぼ同じ. ブレークポイントの設定 スタックのインスペクション ステップ実行 etc. 26 / 39
  • 27.
    typedness のアノテーション Cython およびC/C++コードを typedness により色分けした HTML ファイルを生成する. $ cython foo.pyx -a http://docs.cython.org/src/quickstart/cythonize.html 27 / 39
  • 28.
    プロファイリング 次のディレクティブによりプロファイリングが有効になる. # cython:profile=True 使用方法は cProfile を使った Python のプロファイリングと同じ. cProfile.runctx("extmod.func()", globals(), locals(), "Profile.prof") s = pstats.Stats("Profile.prof") 28 / 39
  • 29.
    その他の機能 Numpy との連携 Sage Notebook との連携 コンパイラディレクティブ 条件付きコンパイル 融合型 型つきメモリビュー 並列化 GIL 制御 etc. 詳しくは http://docs.cython.org/ 29 / 39
  • 30.
  • 31.
    Cython により生成された C/C++ファイルを読むにはPython/C API の知識が必要となる. 以下ではその初歩について説明する. 詳しくは http://docs.python.jp/2/extending/index.html 31 / 39
  • 32.
    すべてのデータはオブジェクト #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.
  • 34.
    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
  • 35.
    PyObject Call*で Pythonオブジェクトの呼び出し PyObject* PyObject_Call(PyObject *callable, PyObject *args, PyObject *kw) args, kw のチェック, 再帰の管理などが行われる ガベージコレクションは参照カウント法 参照カウントの振る舞い “参照の所有権” により理解される Py INCREF, Py DECREF で参照カウンタを増減 参照カウンタが 0 になったオブジェクトは破棄される 35 / 39
  • 36.
    4. Summary 36 / 39
  • 37.
    まとめ Cython を使ってC/C++ライブラリの Python ラッパーを “手軽に” 作ることが出来る 拡張ライブラリの仕組みを把握するには Python/C API の知識が必要 Cython は C/C++と Python の両方の長所を活かすための 橋渡しとしての役割を果たす 37 / 39
  • 38.
    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
  • 39.