C++しか書かずにPythonライブラリを作る

前日の記事は@seshimaruさんによる「[GTK+3で別スレッドから画面を更新する](https://qiita.com/seshimaru/items/640749092864aba8f83c)」、
翌日の記事は@Gacchoさんによる「[C言語とC++の同時比較リファレンス](https://qiita.com/Gaccho/items/fd8db5c393c48ef2a61c)」です。

## Introduction

C++アドベントカレンダーを読んでいる方はC++が大好きなのでご存じないかもしれませんが、人の世ではC++は矮小な人類の理解を超越した深淵から来たりし闇の言語と呼ばれ恐れられています。しかし邪教の力は凄まじく、中には人の身ながらその力を借りたいと願う者もいると聞きます。そういうわけで、人の世で受け入れられているPythonにも、C++の闇の力を纏えるようにしようと思うわけです。

こういう多方面から怒られそうなノリは最初の1パラグラフだけです。

今回は、pybind11を使ってC++で書かれたコードをpythonから呼び出していけるようにしたいわけですが、インストール方法やチュートリアル、他のバインディング方法との比較などはあまり触れません。というのも、その辺りは[参考](#参考)にリストした記事や当然ながら公式ドキュメントで既によくまとまっているからです。ここではもう少し細かいTips集を、記事として体裁を整えて書いていきます。

説明を簡単にするため、画像ファイルを読み込むライブラリのバインディングを書いているということにしましょう。非常に大雑把に、以下のような構成要素があることにします。実装の細かいところは、本筋ではないので飛ばします。

```cpp
enum class pixel_tag {rgb, rgba};

template<typename T> basic_rgb_pixel  {T r, g, b;};
template<typename T> basic_rgba_pixel {T r, g, b, a;};

using  rgb_pixel = basic_rgb_pixel <std::uint8_t>;
using rgba_pixel = basic_rgba_pixel<std::uint8_t>;

struct header; // header informations
template<typename Pixel> class image {header hdr; std::vector<Pixel> data;/*その他便利関数*/};

header read_header(const std::string&);
template<typename Pixel> image<Pixel> read(const std::string&);
template<typename Pixel> void        write(const std::string&, const image<Pixel>&);
```

あと、`namespace py = pybind11;`をしてあるとして進みます。

## 関数のバインディング

普通に関数をバインディングしたいときは、以下のようにします。

```cpp
#include <pybind11/pybind11.h>
namespace py = pybind11;

PYBIND11_MODULE(/*モジュール名*/ example, /*モジュールオブジェクト*/ m) {
    m.def(/*pythonでの関数名*/ "read_header",
          /*バインドする関数*/  &read_header,
          /*named argument*/  py::arg("filename"),
          /*docstring*/       "read header information from file");
}
```

別の関数に分けることもできます。

```cpp
void bind_readheader(py::module& mod) {
    mod.def(/*pythonでの関数名*/ "read_header",
            /*バインドする関数*/  &read_header,
            /*named argument*/  py::arg("filename"),
            /*docstring*/       "read header information from file");
    return
}

PYBIND11_MODULE(example, m) {
    bind_readheader(m);
}
```

注：ここでは当然のように書いていますが、先に戻り値型のクラスをバインディングしておきましょう。

### テンプレート関数をバインディングする

残念ながらこれはできません。`template`は一般の型について実装のテンプレートを作るための機能なので、`template`関数の実体は型が決まる前には存在していません。存在しないものをバインディングできるわけがないですね。

というわけで型を指定して実体化するとバインディングできるようになります。

```cpp
PYBIND11_MODULE(example, m) {
    m.def("read_rgb",
          read<rgb_pixel>, // 型を指定して実体化する
          py::arg("filename"),
          "reads image composed of rgb pixel");
    m.def("read_rgba",
          read<rgba_pixel>, // 型を指定して実体化する
          py::arg("filename"),
          "reads image composed of rgba pixels");
}
```

オーバーロードが可能な場合、つまり実体化によって引数の型が変わる場合（C++では引数からの推論で`template`の型が決まる場合）は関数のオーバーロードと同様のことができます。

```cpp
PYBIND11_MODULE(example, m) {
    m.def("write", write<rgb_pixel>,  py::arg("filename"), py::arg("image"));
    m.def("write", write<rgba_pixel>, py::arg("filename"), py::arg("image"));
}
```

### その場でラッパー関数を実装する & 返す型を動的に変える

さて、いちいちヘッダ情報を読み取ってピクセルのフォーマットを調べてから`read_rgb`とか`read_rgba`とかを呼び分けるのは面倒過ぎますね。この2つは戻り値型が違うので（`image<rgb_pixel>`と`image<rgba_pixel>`）、C++では何らかの形でコンパイル時に型を決めておく必要があり、動的にファイルを読んでから型を変えることはできません。いや継承してればできますが。

ただ、Pythonでは型がそもそも動的に決まるので余裕で型を動的に決められます。なのでそういう関数としてラップしたいですよね。

pybind11ではラムダをラップすることができるので、C++側に関数を漏らすことなくPython用の関数を実装できます。戻り値型は`py::object`にしておきましょう。クラスをバインディングしておけば、`py::cast`によって`py::object`に変換できます。

```cpp
PYBIND11_MODULE(example, m) {
    m.def("read", [](const std::string& fname) -> py::object {
        const auto header = read_header(fname);
        if(header.pixel_format == pixel_tag::rgb) {
            return py::cast(read<rgb_pixel>(fname));
        } else if(header.pixel_format == pixel_tag::rgba) {
            return py::cast(read<rgba_pixel>(fname));
        } else {
            throw std::runtime_error("error: unknown pixel format");
        }
    }, py::arg("filename"));
}
```

便利。

## クラスのバインディング

### メンバ変数をさらけ出す

クラスのメンバ変数は、書き換え可能かどうかなどを指定してバインディングできます。また、第三引数にその変数の説明を書けます。`help`ではこのメッセージが見られるようになります。

```cpp
// template<typename Pixel> class image {header hdr; std::vector<Pixel> data;};

PYBIND11_MODULE(example, m) {
    py::class_<image<rgb_pixel>>(m, "image_rgb", "image composed of rgb pixels")
        .def_readwrite("header", &image<rgb_pixel>::hdr, "header struct");
}
```

### メンバ関数をその場で実装する

例えば`__repr__`などがあると嬉しいでしょう。ちなみにC++では`__`を含む名前は予約されているので使ってはいけません。あと、`_`の後に大文字から始まる（`_M_data`など）名前も予約されています。標準ライブラリがこれを使うのは当然ですが、標準ライブラリ以外が使ってはいけません。

さて、メンバ関数は自分を最初の引数として取るので、そのような関数を作ってバインディングすればokです。

```cpp
PYBIND11_MODULE(example, m) {
    py::class_<header>(m, "header", "header information")
        .def("__repr__", [](const header& self) {
            std::ostringstream oss;
            oss << "x_pixels = " << self.x_pixels;
            oss << "y_pixels = " << self.y_pixels;
            return oss.str();
        });
}
```

### 変数と関数を同名にするとどうなるか

普通やらないと思いますが、同名のメンバ関数とメンバ変数を登録してしまうと、__ビルドが通ります__。普通にライブラリができてしまいますが、インポートした時に以下のようなエラーが出ます。

```console
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: Cannot overload existing non-function object "data" with a function of the same name
```

C++しか書かずに、と言ったものの、それだとこういうのは見つけづらいので、pythonコードのテストも自動でやっておきましょう。

### enum class

ドキュメントでenumを検索すると、[クラス内で定義された`enum`の説明が出てきますが](https://pybind11.readthedocs.io/en/stable/classes.html?highlight=enum#enumerations-and-internal-types)、それに限らず`enum class`も同様にバインディングできます。

```cpp
// enum class pixel_tag {rgb, rgba};

PYBIND11_MODULE(example, m) {
    py::enum_<pixel_tag>(m, "pixel_tag", py::arithmetic())
        .value("rgb",  pixel_tag::rgb)
        .value("rgba", pixel_tag::rgba)
        .export_values();
}
```

まったく同じですね。

## ビルド

Pybind11がCMakeを使っているので、CMakeを使っていきます。多分一番簡単なのは、CMakeスクリプトで`pybind11`をダウンロードして、`add_subdirectory`することでしょう。それだけで全部できるようになります。

あとは、ソースコードのところで

```cmake
set(PYBIND11_CPP_STANDARD -std=c++11)
pybind11_add_module(example SHARED example.cpp)
```

とすればよいです。

### pipでインストールできるようにする

インストールがユーザーにとって一番の鬼門であるというのはよく知られた事実です。エラーが出やすい上に、知識がないと理由がわからず、解決不能に陥ることもあります。とくに、「〇〇と××のバージョンこれこれを先にインストールしておいて、ビルドしたあと`PYTHONPATH`を通す」などと言われると発狂間違いなしです。

そこで、READMEのインストール方法に

```sh
$ git clone　https://github.com/hoge/fuga.git
$ pip install --user ./fuga
```

とだけ書いてあればカッコいいでしょう。Star即ポチ間違いなしです。

要するに`setup.py`があればそれでいいわけですが、`pybind11`は優しいので[サンプルレポジトリに全部置いてくれています。](https://github.com/pybind/cmake_example)

基本的にCMakeでビルドするようにしておいて、このサンプルレポジトリにある`setup.py`を適宜改変すれば全て上手いこと行きます。最高。

### 参考

#### 英語

- https://pybind11.readthedocs.io/en/stable/
    * 公式ドキュメント
- https://github.com/pybind/cmake_example
    * CMakeでビルドするときのサンプル

#### 日本語

基本的にこの辺のを見ると英語情報の大半をスキップできます。

- https://qiita.com/ignis_fatuus/items/c7523c0fe2bc2f415d50
- https://myenigma.hatenablog.com/entry/2016/12/17/075812
- https://kivantium.net/cpp-pybind11
- https://qiita.com/exy81/items/e309df7e33d4ff20a91a