Pybind11をパッケージ化するまでの流れ

![pybind11-logo.png](https://qiita-image-store.s3.amazonaws.com/0/234222/cd7d94d8-9a4e-fb0f-a3c2-39deff6fce77.png)

# 背景
Pythonの`for文`は計算時間が長いという問題があり、そこを高速化したかったので、C++でアルゴリズムを書いて、pythonにインポートするということをしました。
その際、pybind11というパッケージを利用したのですが、まだあまり参考にできる記事が少なかったので、その際のメモを残したいと思います。

# 環境
OS: macOS Mojave 10.14.2
python: pyenv 3.6.5


# Pybind11とは

公式github: https://github.com/pybind/pybind11
公式ドキュメント: https://pybind11.readthedocs.io/en/stable/

pybind11とは公式ドキュメントによると
> pybind11 is a lightweight header-only library that exposes C++ types in Python and vice versa, mainly to create Python bindings of existing C++ code.

ということです。つまり、PythonにC++のコードを、またC++にPythonのコードを繋げることができるライブラリです。

実はこういったことを行ってくれる他の1つのライブラリもあり、その1つにBoost.Pythonというのがあるらしいです。が、自分は触ったことがないので、[こちらの参考サイト](https://qiita.com/ignis_fatuus/items/ad90da660c7b6a38fffc)のみ載せておきます。

Pybind11とBoostの大きな違いはBoostはとても大きいライブラリだということです。単純なモジュールを呼ぶためだけにBoostを使うのは非効率的ということもあり、Pybind11は作成されました。

その辺の詳しいベンチマークなどは[こちら](https://pybind11.readthedocs.io/en/stable/benchmark.html)に詳しく書いてあるので、興味あればみてみてください！

## インストール方法

インストール方法は何通りかあるのですが、今回自分は`pip`を使ったのでそちらの方の紹介をします。

```shell
$ pip3 install pybind11
```

この他にも`github`からのインストール方法もあるのですが、実行方法が異なってくる（コンパイルする際のコマンドが違うため）ので、気をつけた方がいいです！

# 実装例（簡易版）

背景にも書いたのですが、自分はpythonの`for文`の計算量が多いのが問題となっていたので、配列を用いたモジュールを書きました。が、公式にも英語のサイトにも中々配列の使い方が綴られておらず（自分の裁量不足ということもあるのですが...）、試行錯誤しました。
その結果、動いたコードの簡略版で実装例を紹介します。

## コード

```c++

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

void print_arrays(std::vector<int> lst) {
  // 配列の長さ
  int length = sizeof(lst)/sizeof(lst[0]);

  // 配列の各要素を出力する
  for (int i=0; i < length-1; i++) {
    printf("%d\n", lst[i]);
  }
}

// PYBIND11_MODULE()はPythonでimportする際の関数を作成する
PYBIND11_MODULE(example, m) {
  m.doc() = "Example pybind11"; // optional module docstring
  m.def("print_arrays", &print_arrays, "A function that prints array elements.");
}
```

## 実行例

あとは実行するだけです！が、ここで私は躓きました。

### 失敗したコマンド

上は色んなサイトを調べた結果上手くいったコマンドです。
そして、実際に多くの人が行うように、私も公式ドキュメントに従って、以下のコマンドを実行をしました。

```shell
c++ -O3 -Wall -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` example.cpp -o example`python3-config --extension-suffix`
```
その結果、バーっとエラーが吐かれ（長すぎるため省略します）、最後に

```shell
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```
と言われてしまいました。

色々調べた結果、[こちら](https://github.com/pybind/pybind11/issues/382)のイシューが参考になりました。

### 成功したコマンド
そこで、上記のサイトを参考に、以下のコマンドで**成功**しました。

```shell
clang++ -O3 -Wall -shared -std=c++11 -fPIC `python -m pybind11 --includes` -undefined dynamic_lookup example.cpp -o example`python3-config --extension-suffix`
```
その結果、同じディレクトリに`example.cpython-36m-darwin.so`という名前のファイルができたと思います。

詳しくは調べきれていないのですが、多分`pyenv`の影響かと思うので、**使っている人は気をつけてください！**

### 実行結果

ここまで来ればあとは簡単です。
先ほどのコード内で`PYBIND_MODULE()`という関数を用いて、pythonでインポートする際の関数を生成しました。

あとは、REPLで以下のようにすればいけます。

```shell
$ python3
>>> import example
>>> example.print_arrays([1, 2, 3, 4, 5])
1
2
3
4
5
>>>
```

# 応用

ここまでやると、本格的にライブラリを作りたくなってきました。
というのも、pybind11の公式ドキュメントに[ビルドシステム](https://pybind11.readthedocs.io/en/stable/compiling.html)というセクションがあり、自分が作ったライブラリを実際に`pip`でインストールできるようになるまでが書かれていたからです（Pypiに載せるところまではやらないです）。

## ディレクトリ構成

ディレクトリ構成は人それぞれの好みがあると思いますが、今回は公式ドキュメントでも紹介されていた[gitのプロジェクト](https://github.com/pybind/python_example)を参考にしました。

```shell
pybind11_example/
　├ src/
　│　├ addition.cpp
　│　├ subtraction.cpp
　│　└ main.cpp
　├ tests/
　│　└ test.py
　├ setup.py
　└ README.md
```

## src/内のコード

src内には任意のファイルを作って大丈夫です！

配列の各要素を足し合わせる関数を格納したファイル

```c++:addition.cpp
#include <pybind11/stl.h>

int sum_arrays(std::vector<int> lst) {
  // 配列の長さ
  int length = lst.size();
  int sum = 0;

  // 配列の各要素を出力する
  for (int i=0; i < length; i++) {
    sum += lst[i];
  }

  return sum;
}
```
配列の各要素の積を返す関数を格納したファイル

```c++:product.cpp
#include <pybind11/stl.h>

int product_arrays(std::vector<int> lst) {
  // 配列の長さ
  int length = lst.size();
  int pro = 1;

  // 配列の各要素を出力する
  for (int i=0; i < length; i++) {
    pro *= lst[i];
  }

  return pro;
}
```

上記のモジュールを束ねるファイル`main.cpp`

```c++:main.cpp
#include <pybind11/pybind11.h>

#include "addition.cpp"
#include "product.cpp"

PYBIND11_MODULE(pybind11_example, m) {
  m.doc() = "Example pybind11"; // optional module docstring
  m.def("sum_arrays", &sum_arrays);
  m.def("product_arrays", &product_arrays);
}
```
ここで、今回はライブラリ名を`pybind11_example`と設定しました。これは後にpipでインストールする際に同じ名前を用いるので、気をつけてください。

## setup.py

あとは`setup.py`を記述するだけです。（README.mdやtest/内は省略をします）
これはpybind11の公式ドキュメントに[ビルドシステム](https://pybind11.readthedocs.io/en/stable/compiling.html)をかなり参考にしました。

基本真似をして、`ext_modules=[]`と`setup()`の中身を書き換えるだけです。

```c++:setup.py
...
ext_modules = [
    Extension(
        'pybind11_example', // ライブラリ名
        ['src/main.cpp'], // 参照にするファイル
        include_dirs=[
            # Path to pybind11 headers
            get_pybind_include(),
            get_pybind_include(user=True)
        ],
        language='c++'
    ),
]
...
...
setup(
    name='pybind11_example', // ライブラリ名
    version=0.1,
    author='sff1019',
    description='A pybind11 example',
    long_description='',
    ext_modules=ext_modules,
    install_requires=['pybind11>=2.2'],
    cmdclass={'build_ext': BuildExt},
    zip_safe=False,
)
```
これで完成です！

## 実行

`pybind11_example11`の親ディレクトリに入ります。

```shell
$ pip install ./pybind11_example
Processing ./pybind11_example
...                                                                         
Successfully installed pybind11-example-0.1
```
となれば成功です。

あとは好きなところで

```python
import pybind11_example

pybind11_example.sum_arrays([1,2,3])
```
のように実行ができます。
ちなみに、出力は以下のようになります。

```shell
6
```

# 最後に

最初のpyenvのところだけが突っかかりましたが、思った以上に簡単にC++のコードをpythonにインポートすることができるのだな、と感じました。


# 参考サイト
- [pybind11でC++の関数をpythonから使う](https://qiita.com/ignis_fatuus/items/c7523c0fe2bc2f415d50)
- [pybind11を使ってみる](https://qiita.com/takuyakubo/items/b7503d7555bbc6c44aba)
- [python_example](https://github.com/pybind/python_example)