pybind11 で C++ のバインディングを書くメモ
pybind11, 機能はあるけど document はいろいろ書いてある割りには貧疎だし, exmaple コードなくてつらいね...
test コードはあるがいろいろ記述されているためわかりにくいし, pybind11 のコード自体template バリバリでようわからんし...
float, double の順
float と double の両方を取り得る同名メソッドの場合, 定義順で評価になるようです.
.def("set", float ...)
.def("set", double ...)
python は double が標準ですから, double なメソッドを先に定義するとよいでしょう.
明示的に C の型を指定したい場合は, ctypes を使うか
自前ライブラリで binding 書いて用意することになるでしょう(float16
などの型とか)
def_readwrite
とかしたい
メソッドに対して class Myclass {
private:
std::vector<int> _myarr;
public:
std::vector<int> &myarr() { return _myarr; }
const std::vector<int> &myarr() const {
return _myarr;
}
のような, メソッドしか公開されていない & setter, getter が同じ関数名の場合に, Python 側からは変数のようにアクセスしたい(MyClass.myarr = [1, 2, 3]
みたいな).
まず, def_readwrite
ではC++ メソッドを指定できない.
(なんかテンプレートとかの記述がんばればいけるかもだけどようわからん...)
def_readwrite
自体は def_property
への wrapper でしたので, def_property
で対応します.
方法1. ラムダ関数利用
C++ で setter, getter が同じ名前だとうまくいきません. 以下のように getter では明示的に const を返すように指定します!
(そうしないと Python 側でうまくいかない)
py::class_<MyClass>(m, "MyClass")
.def(py::init<>())
.def_property("myarr", [](MyClass &self) -> const std::vector<int> & {
py::print("intv get");
return self.myarr();
}, [](MyClass &self, const std::vector<int> &v) {
py::print("intv set");
self.myarr() = v;
})
readonly にしたい場合は setter に nullptr, writeonly にしたい場合は getter に nullptr 指定でいけます.
方法2. 明示的型指定
void (MyClass::*)(bool)
などで, メソッド関数のシグネチャの関数ポインタのキャストでいけました.
(A::*
とか C++ 歴 30 年くらいではじめてミタ! ややこしいね)
.def_property("myarr",
static_cast<const std::vector<int>&(MyClass::*)() const>(&MyClass::myarr), ..., py::return_value_policy::reference_internal)
py::return_value_policy::reference_internal
をつけておくと安心かも
(getter で返るのは C++ 側インスタンスのリファレンス)
setter も同様ですが, 今回のケースでは対応する関数シグネチャ(void set_myarr(const std::vector<int> &v)
が C++ クラス側に無いので, 今回の場合は引き続きラムダを使うことに
なるでしょう.
C++14 の場合は py::overload_cast
でもうちょっと簡略化できます.
const な method の場合は py::const_
をつけます.
py::overload_cast<>(&MyClass::myattr, py::const_));
また, static method の場合はクラスの部分は (*)
になります(C での関数ポインタと同じ).
class Dora {
public:
static void bora(bool v);
};
m.def_static(“bora", (void (*)(bool)) &Dora::bora);
std::vector
あとは, std::vector
などつかっていて python list 的に処理したい場合は, opaque 型定義しておく必要があります.
これで以下のような感じで python 的にプロパティアクセス + リストアクセス(?)できます.
k = MyClass()
k.myarr.append(3)
ただ, std::vector
を opaque type にすると, 明示的な型で配列(リスト)を作る必要があります.
k.myarr = [1, 3] # NG
k.myarr = VectorInt([1, 3]) # OK
より pythonic にするなら, 引数や返り値など C++ STL container 型ではなく py::list
で扱うようにしたほうがいいのかも?
keepalive について
keep_alive<Nurse, Patient>
とややこしい名称になっている.
Nurse, Patient には, メソッドの場合引数の番号を指定する.
0 : 返り値型
1 : this
2 : 関数の第一引数
ふるまいとしては, Nurse
が fee されるまでは Patient
は保持されることを指定する.
通常のメソッドでは <1, 2>
で(インスタンスthis
が解放されるまで),
iterator の場合は <0, 1>
(返したオブジェクトが解放されるまで)
となろうか.
不明点
k.myarr.append
だとしかし実は const 返しの getter しか呼ばれていない(setter 関数は呼ばれない)
コンパイル遅い...
どうしようもないです...
C 関数用意して, Python C API で対応するか, ctypes
で DLL 直叩きも検討してみましょう.
TODO
- C++ の型によっては return value policy を適切に設定する必要があるであろう
Discussion