pybind11 v2.6 or later を想定します.
pybind11 では, numpy データ(クラス)が大体一通り用意されています.
ドキュメントにはあまり詳しい記述がなさそうですので, 詳細は .h や test code を見ることになりそうです.
py::buffer
or py::array
py::buffer
は Python buffer protocol を使って生バイナリを扱うような感じです. numpy 自体も内部データ構造では buffer protocol を利用しており, C++ 側で扱うぶんには NumPy(の C コード) は不要なのですが, Python 側からは py::array
の場合は numpy が必要になります.
Python 側で numpy 使いたくない場合は py::buffer
なりで頑張ることになります.
py::buffer_info
, py::buffer
def_buffer
でのみ利用できます.
def
などでメソッドとして buffer_protocol を返すことはできません.
Unable to convert function return value to a Python type!
とエラーが出ます...
専用の C++ class を作ることになります... ちょいめんどい...
グローバル配列定数などであれば memoryview を使うのもできます.
py::array
前述のとおり, pybind11 の配列表現 py:array
or 明示的な型指定の py::array<T>
は, numpy 配列として扱われます.
Python 標準の array
モジュール(array.array
) の array
型にマップされるわけではないので注意ください(ややこしいですね).
また, pybind11 では Python native の array
モジュールに対応する binding は提供されていません...
pybind11 内部および numpy 自体では, 実際のところはこの配列(numpy::array
型)は Python buffer protocol として実装されています.
pybind11 の実装としては numpy の型として見せるのにに必要な定義は pybind11 のヘッダにあるので, 別途 numpy(C) のヘッダなどはコンパイルするのには不要ですが, python 側からのアクセスでは(C++
側から numpy
を import しているため), numpy が必要になります.
py::print
で, C++ 側で Python print
相当ができます. デバッグに役立つでしょう.
e.g.
py::array_t<double> nd;
py::print("ndarray = ", nd);
ただ, shape()
はポインタを返すため, そのままでは shape
情報を print でうまく行えません.
とりあえずは helper 関数を書いて,
template<typename T>
std::string to_string(T *ptr, size_t n)
{
std::ostringstream ss;
ss << "(";
for (size_t i = 0 ;i < n; i++) {
ss << ptr[i];
if (i != (n - 1)) {
ss << ", ";
}
}
ss << ")";
return ss.str();
}
py::print("shape = ", to_string(nd.shape(), nd.ndim()));
のようにします.
dtype
dtype はそれ自体が python object(py::dtype
) になっています.
型の比較
is()
で比較できます.
dtype は of()
などで生成することがきます(文字列で指定して dtype オブジェクト生成も可能).
==
や !=
での比較は推奨されません(warning が出る)
py::array a = ...;
auto f32ty = py::dtype::of<float>();
if (!f32ty.is(a.dtype())) {
}
dtype の文字列表現
型が合わないなどで, dtype の文字列表現を取得してエラーメッセージを出したいときがあります.
py::str(a.dtype())
として py::str
オブジェクトを作ります.
(a.dtype().str()
は推奨されず warning になる)
文字列表現は py::str
で python オブジェクトのままなので, std::string
に変換したい場合は明示的にキャストが必要です.
void from_numpy(const py::array &a) {
auto f32ty = py::dtype::of<float>();
if (!f32ty.is(a.dtype())) {
throw py::type_error("`float32` dtype expected but input array has dtype `" + std::string(py::str(a.dtype())) + "`");
}
...
}
値の取得
data()
で取得できます. 多次元配列の場合は data(0, 3)
などとできます.
形無し(py::array
)だと void *
でデータが返ります.
ほしい型が既知の場合は, 明示的にキャストしてもよいですが, 以下のように py::array_t<T>
のオブジェクトを作るとよいでしょうか.
(内部ではコピーを作ってしまうのかな?)
py::array a = ...;
py::array_t<float> f(a);
const float *v = f.data(0);
値の設定
基本配列データは const なので mutable_***
な関数で const 外し(mutable アクセス)が必要です.
static py::array_t<double> gen_arr(int n)
{
py::array_t<double> a;
a.resize({n, n});
for (size_t i = 0; i < a.shape(0); i++) {
for (size_t j = 0; j < a.shape(1); j++) {
a.mutable_at(i, j) = double(i + j);
}
}
return a;
}
memcpy とかで一括処理したい場合は, buffer protocol にして, ptr
で生ポインタでアクセスが楽でしょうか.
py::array<float> arr;
py::buffer_info b = arr.request();
const float *ptr = static_cast<float *>(b.ptr);
データにアクセスするその他の方法が document にあります.