1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

pybind11 で numpy データ(`py::array`)を扱うメモ

Last updated at Posted at 2020-11-01

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 が必要になります.

print

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 にあります.

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?