2018/12/21にPyData Osakaの梅キャンPython勉強会コラボ回でpybind11について発表しました
2018/12/21(金)にPyData Osakaの梅キャンPython勉強会コラボ回でpybind11について発表しました¶
ブログ記事にするのが遅くなりましたが2018/12/21に【大阪工業大学】特別回 梅キャンPython勉強会【梅田キャンパス】 でpybind11に関する発表を行いました。 下記はその際の資料になります。
In [1]:
from IPython.display import IFrame
IFrame("https://www.hiromasa.info/slide/14.slides.html", "100%", "450px")
Out[1]:
pybind11の紹介¶
大橋宏正(PyDataオーガナイザー)
自己紹介¶
- 大橋 宏正(@wrist)
- PyDataオーガナイザー
- 某メーカー勤務
- 音響信号処理屋
pybind11とは¶
- C拡張モジュールをc++11で作成するためのライブラリ
- ヘッダオンリー
- numpy, Eigen連携
- https://ep2017.europython.eu/media/conference/slides/pybind11-seamless-operability-between-c11-and-python.pdf
他のライブラリとの比較¶
- Python C API
- 自分で参照カウント制御が必要
- 学習障壁が高い
- ctypes
- dllがあればビルド不要で使える
- 返り値や引数の型を厳密に設定する必要有
- cython
- pyxの独自記法を覚える必要
- 一旦pyxファイルをcにコンパイル
- 更にcファイルをコンパイル
- Boost.python
- Boostが必要
- 便利だが巨大でありコンパイルに時間がかかる
インストール¶
- https://github.com/pybind/pybind11
- pipでインストール可能
In [1]:
!pip install pybind11
ipybind¶
- pybind11のjupyter拡張
- jupyter notebookのセルから実行可能
- https://github.com/aldanor/ipybind
ipybindのインストール¶
- リポジトリをクローン
python setup.py build
python setup.py install
使ってみる¶
- ipybind経由でpybind11を使用
- 最初に
load_ext
でipybindを有効化
In [2]:
%load_ext ipybind
pybind11の基本¶
PYBIND_MODULE(モジュール名, m)
m.def(公開名, 関数へのポインタ, 説明)
In [3]:
%%pybind11
int add(int x, int y){
return x + y;
}
PYBIND11_MODULE(myadd, m){
m.def("c_add", &add, "Add two integers.");
}
In [4]:
c_add(1, 2)
Out[4]:
In [5]:
help(c_add)
In [6]:
# c_add("foo", "bar")
In [7]:
%%pybind11
PYBIND11_MODULE(myadd, m){
m.def("lambda_add", [](int a, int b){ return a + b;},
"Add two integers.");
}
In [8]:
lambda_add(1, 2)
Out[8]:
その他のコンパイル方法¶
- 手動ビルド
- distutils
- setup.pyを書く
- cmake
pybind11_add_module(myadd myadd.cpp)
単純なクラスをラップ¶
- c++のクラスを定義
- pybind11でラップ
py::classs_<C++クラス名>(m, 公開クラス名)
In [9]:
%%pybind11
namespace py = pybind11;
class Coordinate {
public:
int x_;
int y_;
Coordinate(){ x_ = 0; y_ = 0; }
Coordinate(int x, int y){ x_ = x; y_ = y; }
float Norm(){ return sqrt(x_*x_ + y_*y_); }
};
PYBIND11_MODULE(coord, m) {
py::class_<Coordinate>(m, "Coordinate")
.def(py::init<>())
.def(py::init<int, int>())
.def_readonly("x", &Coordinate::x_)
.def_readonly("y", &Coordinate::y_)
.def_property_readonly("norm", &Coordinate::Norm);
}
コンストラクタの定義¶
... .def(py::init<オーバーロード引数>())
Coordinate(){ x_ = 0; y_ = 0; }
Coordinate(int x, int y){ x_ = x; y_ = y; }
は下記のように記載
py::class_<Coordinate>(m, "Coordinate")
.def(py::init<>())
.def(py::init<int, int>())
プロパティの定義¶
... .def_readonly(公開変数名, メンバへのポインタ)
py::class_<Coordinate>(m, "Coordinate")
.def_readonly("x", &Coordinate::x_)
.def_readonly("y", &Coordinate::y_)
computed propetyの定義¶
... .def_propety_readonly(公開変数名, 計算関数へのポインタ)
float Norm(){ return sqrt(x_*x_ + y_*y_); }
は
.def_property_readonly("norm", &Coordinate::Norm);
In [10]:
point = Coordinate(3, 4)
point
Out[10]:
In [11]:
print(point.x, point.y)
In [12]:
point.norm
Out[12]:
さらなる例¶
- 演算子オーバーロード、python特殊メソッド
- docstring、引数名、キーワード引数
*arg, **kwarg
演算子オーバーロード¶
- 等値演算子
- cppだと
bool operator==(const Cls& rhs)
- pythonだと
Cls.__eq__(self, other)
- これらを対応付ける
- cppだと
In [13]:
int.__eq__(1, 1)
Out[13]:
In [14]:
%%pybind11
namespace py = pybind11;
class Coordinate2 {
public:
int x_; int y_;
Coordinate2(){ x_ = 0; y_ = 0; }
Coordinate2(int x, int y){ x_ = x; y_ = y; }
bool operator==(const Coordinate2& rhs){
return (x_ == rhs.x_) && (y_ == rhs.y_);
}
};
PYBIND11_MODULE(coord2, m) {
py::class_<Coordinate2>(m, "Coordinate2")
.def(py::init<>())
.def(py::init<int, int>())
.def_readonly("x", &Coordinate2::x_)
.def_readonly("y", &Coordinate2::y_)
.def("__eq__", &Coordinate2::operator==);
}
In [15]:
point1 = Coordinate2(3, 4)
point2 = Coordinate2(3, 4)
point3 = Coordinate2(1, 2)
In [16]:
point1 == point2
Out[16]:
In [17]:
point1 == point3
Out[17]:
演算子の代替記法¶
.def("__eq__", &Coordinate2::operator==);
はc++のクラスでoperator==
が定義されていれば下記のようにも書ける
.def("__eq__",
[](const Coordinate2& self,
const Coordinate2& other){
return self == other;
});
Python特殊メソッド¶
-
__repr__
の定義を考える - 先の
__eq__
と同様に第一引数にselfを取るメソッドを定義
In [18]:
%%pybind11
namespace py = pybind11;
class Coordinate3 {
public:
int x_; int y_;
Coordinate3(){ x_ = 0; y_ = 0; }
Coordinate3(int x, int y){ x_ = x; y_ = y; }
};
PYBIND11_MODULE(coord3, m) {
py::class_<Coordinate3>(m, "Coordinate3")
.def(py::init<>())
.def(py::init<int, int>())
.def_readonly("x", &Coordinate3::x_)
.def_readonly("y", &Coordinate3::y_)
.def("__repr__" , [](const Coordinate3& self){
return std::string() + "(x, y) = (" + std::to_string(self.x_) + ", " + std::to_string(self.y_) + ")";
});
}
.def("__repr__", [](const Coordinate3& self){
return std::string() +
"(x, y) = (" +
std::to_string(self.x_) +
", " +
std::to_string(self.y_) + ")";
});
In [19]:
point = Coordinate3(1, 2)
point
Out[19]:
docstirng、引数名、キーワード引数¶
- docstring
-
m.def
の第三引数に文字列を渡す
-
- 引数名
- 第四引数以降に
py::arg("...")
を渡す
- 第四引数以降に
- キーワード引数
-
py::arg("...") = デフォルト値
とすれば良い
-
In [20]:
%%pybind11
namespace py = pybind11;
class Coordinate4 {
public:
int x_; int y_;
Coordinate4(){ x_ = 0; y_ = 0; }
Coordinate4(int x, int y){ x_ = x; y_ = y; }
float DistanceTo(const Coordinate4& target){
int dx = target.x_ - x_, dy = target.y_ - y_;
return sqrt( dx*dx + dy*dy);
}
};
PYBIND11_MODULE(coord4, m) {
py::class_<Coordinate4>(m, "Coordinate4")
.def(py::init<>())
.def(py::init<int, int>())
.def_readonly("x", &Coordinate4::x_)
.def_readonly("y", &Coordinate4::y_)
.def("distance_to", &Coordinate4::DistanceTo,
"distance to target",
py::arg("target"));
}
float DistanceTo(const Coordinate4& target){
int dx = target.x_ - x_, dy = target.y_ - y_;
return sqrt( dx*dx + dy*dy);
}
を下記のように設定
.def("distance_to", &Coordinate4::DistanceTo,
"distance to target",
py::arg("target"));
In [24]:
point = Coordinate4(1, 2)
target = Coordinate4(3, 4)
point.distance_to(target)
Out[24]:
In [40]:
point.distance_to?
*args, **kwarg
¶
-
py::args
(py::tuple
のサブクラス) -
py::kwargs
(py::dict
のサブクラス)
m.def("count_args", [](py::args a, py::kwargs kw) {
py::print(a.size(), "args,", kw.size(), "kwargs");
});
その他の話題¶
- C++とPython間のオブジェクトの取扱
- 関数とコールバック
- Numpy対応
C++とPython間のオブジェクトの取扱¶
- C++とPythonのオブジェクトを混在してラッパーを定義可能
- 3種類の方法
- Python側でC++の型のインスタンスを引数に取る
- C++側でPythonの型のインスタンスを引数に取る
- C++の型とPythonの型を型変換する
Python側でC++の型のインスタンスを引数に取る¶
py::class_<Foo>(m, "Foo");
m.def("f1", [](const Foo& foo){ ...}
C++側でPythonの型のインスタンスを引数に取る¶
m.def("f2", [](py::list list){ ... }
C++の型とPythonの型を型変換する¶
m.def("f3", [](int x) { ... });
m.def("f4", [](const std::string& s) { ... });
m.def("f5", [](const std::vector<int>& v) { ... });
サポートされている型変換¶
- スカラー値
- integer types, float, double, bool, char
- 文字列
- std::string, const char *
- タプル
- std::pair<F, S>, std::tuple<...>
- シーケンス
- std::vector
, std::list , std::array<T, n>
- std::vector
- マップ
- std::map<K, V>, std::unordered_map<K, V>
- 集合
- std::set
, std::unordered_set
- std::set
- 関数
- std::function<...>
- Date/time
- std::chrono::duration, std::chrono::time_point
- Optional
- std::optional
, std::experimental::optional
- std::optional
関数とコールバック¶
-
std::function
でpythonの関数を受け取れる
In [38]:
%%pybind11
#include <pybind11/functional.h>
PYBIND11_MODULE(callback_test, m) {
m.def("for_even",
[](int n, std::function<void(int)> f) {
for (int i = 0; i < n; ++i){ if (i % 2 == 0) f(i); }
}
);
}
In [39]:
def py_callback_func(x):
print("called {0}".format(x))
for_even(10, py_callback_func)
numpy対応¶
#include <pybind11/numpy.h>
-
py::array_t<type>
型- ndarrayを受け取ることが可能
-
arr.ndim
,arr.shape(n)
,arr.size()
- バッファへのポインタ
-
arr.data()
,arr.mutable_data()
- インデックス指定も可能
-
- 要素への直接アクセス
-
arr.unchecked()
,arr.mutable_unchecked()
-
ref(i, j, k)
としてアクセス可能
-
In [31]:
%%pybind11
#include <pybind11/numpy.h>
namespace py = pybind11;
PYBIND11_MODULE(numpy_bind, m) {
m.def("add_2d",
[](py::array_t<double> x, py::array_t<double> y){
auto r = x.mutable_unchecked<2>();
auto c = y.unchecked<2>();
for(ssize_t i = 0; i < r.shape(0); i++){
for(ssize_t j = 0; j < r.shape(1); j++){
r(i, j) += c(i, j);
}
}
}, py::arg().noconvert(), py::arg().noconvert());
}
In [33]:
import numpy as np
In [34]:
x = np.array([[1.0, 2.0], [3.0, 4.0]])
y = np.array([[2.0, 3.0], [4.0, 5.0]])
add_2d(x, y)
x
Out[34]:
まとめ¶
- Python向けのC拡張モジュールをC++11で作成するためのライブラリpybind11について紹介
- ipybindを用いたJupyter Notebookからの実行方法の紹介
- numpy連携を紹介
コメント
Comments powered by Disqus