TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

Bazel+pybind11でC++のコードからPythonモジュールを作る

個人メモ

Bazel+pybind11でC++のコードからPythonモジュールを作る方法について記述する。
以下では、Ubuntu 22.04のDockerコンテナを使用している。

Bazelのインストール

公式の推奨に従って、Bazeliskを使用する。

npmのインストール

Ubuntuのaptからインストールした後、バージョンアップする。

apt install nodejs npm
npm install -g n
n stable
apt purge nodejs npm
Bazeliskのインストール
npm install -g @bazel/bazelisk

Python3とNumpyのインストール

apt install python3-numpy -y

作業ディレクトリ作成

適当に作業ディレクトリを作成し、cdする。

mkdir bazel_python
cd bazel_python

WORKSPACEファイル作成

作業ディレクトリにWORKSPACEファイルを作成する。
内容は、pybind11_bazelのREADMEに従う。

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
  name = "pybind11_bazel",
  strip_prefix = "pybind11_bazel-master",
  urls = ["https://github.com/pybind/pybind11_bazel/archive/master.zip"],
)
# We still require the pybind library.
http_archive(
  name = "pybind11",
  build_file = "@pybind11_bazel//:pybind11.BUILD",
  strip_prefix = "pybind11-2.10.4",
  urls = ["https://github.com/pybind/pybind11/archive/v2.10.4.tar.gz"],
)
load("@pybind11_bazel//:python_configure.bzl", "python_configure")
python_configure(name = "local_config_python", python_version = "3")

C++のソースディレクトリ作成

C++のソースを格納するディレクトリを作成する。

mkdir src

BUILDファイル作成

srcディレクトリに、BUILDファイルを作成する。

load("@pybind11_bazel//:build_defs.bzl", "pybind_extension")

pybind_extension(
    name = "example",
    srcs = ["example.cpp"]
)

C++のソース作成

srcディレクトリに、C++のソース(example.cpp)を作成する。
以下の例では、

  • 2つの正数を足すadd関数
  • リストをプリントするprint_vector関数
  • Numpyのndarrayをプリントするprint_ndarray関数
  • Numpyのndarrayを返却するget_ndarray関数

の4つの関数を定義している。

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include <iostream>

namespace py = pybind11;

int add(int i, int j) {
    return i + j;
}

void print_vector(const std::vector<int> &v) {
    for (auto item : v)
        std::cout << item << " ";
}

void print_ndarray(py::array_t<int16_t> array) {
    std::cout << array.size() << std::endl;
    for (auto item : array)
        std::cout << item << " ";    
}

py::array_t<int8_t> get_ndarray() {
    constexpr size_t size = 3;
    auto result = py::array_t<int8_t>(size);
    py::buffer_info buf = result.request();
    int8_t *ptr = static_cast<int8_t*>(buf.ptr);
    for (size_t i = 0; i < size; ++i)
        ptr[i] = (int8_t)i;
    return result;
}

PYBIND11_MODULE(example, m) {
    m.def("add", &add, "A function that adds two numbers");
    m.def("print_vector", &print_vector);
    m.def("print_ndarray", &print_ndarray);
    m.def("get_ndarray", &get_ndarray);
}

ビルド

bazeliskコマンドでビルドする。
ビルドターゲットは、BUILDファイルのpybind_extensionで定義したnameに拡張子.soを付けたものになる。
この例では、example.soとなる。

bazelisk build -c opt src:example.so

ビルドに成功すると、「bazel-bin/src/example.so」にPythonモジュールが出力される。

テスト

bazel-bin/srcに移動して、pythonインタープリタから、テストを行う。

cd bazel-bin/src
python3
>>> import example
>>> import numpy as np
>>>
>>> example.add(1, 2)
3
>>> example.print_vector([1, 2, 3])
1 2 3 >>>
>>> example.print_ndarray(np.array([4, 5, 6], dtype=np.int16))
3
4 5 6 >>>
>>> example.get_ndarray()
array([0, 1, 2], dtype=int8)

まとめ

Bazel+pybind11でC++のコードからPythonモジュールを作る方法について記述した。
また、Numpyのndarrayを入力する場合と、出力する場合について例を示した。