C++上でPythonのコードを利用する(Boost.Pythonの利用)

# 追記: 乱用注意！

あなたがもし、面倒な処理をC++で書くのを放棄してPythonで"楽をする"ためにこの記事の方法を使おうとしているなら、絶対にやめたほうが良いです。
環境間でのPythonやパッケージのインストール状況による問題など、保守のために後々で痛い目にあいます。

この記事の方法は、目的のライブラリがPythonからしか使えない場合など、**どうしても必要な場合の最終手段**として使用することをお勧めします。

基本的には、C++用のライブラリを使う、C++から`std::system("python3 xxx.py")`などでPythonコマンドを叩く、またはPythonのみでコードを完結させることをお勧めします。
(例えば、OpenCVやPytorchはC++から直接扱えますし、行列計算もnumpyの代わりにC++向け行列ライブラリのEigenを使えば良いでしょう。)

# はじめに
Boost.Pythonは、C++を使ってPython用ライブラリを作る際に使われることが多いですが、逆にPythonのコードをC++側から利用するいわゆる"埋め込み"(Embedding)の用途でも使用できます。

PythonのコードをC++から利用する需要はあまり多くないのか、"boost python"等で検索しても逆の用途(PythonからC++のコードを使用する場合)についての記事ばかり引っかかります。
その場合は"embed"(埋め込み)という検索キーワードを含めると、C++にPythonを埋め込む場合の記事がヒットしやすいです。

# 最もシンプルなコード例

```cpp:main.cpp
#include <boost/python.hpp>
namespace py = boost::python;

int main()
{
    Py_Initialize(); // 最初に呼んでおく必要あり

    try
    {
        // Pythonで「print('Hello World!')」を実行
        py::object global = py::import("__main__").attr("__dict__");
        py::exec("print('Hello World!')", global);
    }
    catch (const py::error_already_set &)
    {
        // Pythonコードの実行中にエラーが発生した場合はエラー内容を表示
        PyErr_Print();
    }

    return 0;
}
```
このサンプルでは、`py::exec()`関数を使用して`print('Hello World!')`というPythonコードを単純に実行しています。
`global`という変数にPython上のグローバル名前空間のオブジェクトを取得することで、コードをPython上のグローバル名前空間で実行しています。

Boost.Pythonは背後でPythonのCインタフェースを呼んでいるので、使用する前に`Py_Initialize()`関数を呼んでおく必要があります。
(※なお、公式チュートリアルによれば`Py_Finalize()`はコード内で呼んではいけないようです)

また、Pythonで実行時エラーが発生した場合は`py::error_already_set`型の例外が送出されるので、これをcatchして`PyErr_Print()`で内容を表示しています。

## コンパイル方法(Linux+GCCの場合)

Linux+GCCの場合、以下のコマンドでコンパイルできます。

```bash:コンパイル方法
$ g++ main.cpp `pkg-config python3-embed --cflags --libs` -lboost_python38
```
環境によっては`python3-embed`がないバージョンの場合があるので、適宜`pkg-config python3`に変更してください(`python3-embed`があるバージョンでは元の方から埋め込み用のライブラリが除外されているので、`pkg-config python3`を使うとリンクエラーになる場合があります)。

また、上記のうち`boost_python38`の部分はバージョンや環境によって異なります(`boost_python-py38`の形式の場合あり)。

リンクすべきBoost.Pythonのライブラリ名がわからない場合は、以下のコマンドで調べることができます。
出てきた`lib＊＊.so`の`＊＊`の部分がライブラリ名です。

```bash:Boost.Pythonのライブラリ名がわからない場合の調べ方
$ ldconfig -p | grep "boost_python3"
```

# 基本的な使い方
ここからは、具体的な関数ごとに使い方を見ていきます。

以降、`namespace py = boost::python;`と書いてあることを想定して、`py`と表記します。
また、コード中の`global`は最初のサンプルと同じく、以下のコードで取得したグローバル名前空間のオブジェクトです。

```cpp
py::object global = py::import("__main__").attr("__dict__");
```

### ■ `py::eval()`: Pythonで式を評価し、結果を返す
Pythonの式を文字列で与えると、結果を`py::object`型で返してくれます。
以下のように`py::extract<型名>`と組み合わせると、結果をC++の型で取得することができます。

```cpp
// "2**3"の結果を取得
py::object result = py::eval("2**3", global);

// 結果をint型に変換して出力
std::cout << py::extract<int>(result) << std::endl;
```
```:実行結果
8
```

この例の場合はPython内の関数などは特に使用していないので、第2引数の名前空間`global`を省略しても動きます。
ただし、第2引数を省略した場合は、`pow`などのPython組み込み関数や、別途自分で定義した変数・関数が使えなくなるので注意してください。

### ■ `py::exec()`: Pythonコードを文字列で渡して実行
最初のサンプルでもあった通り、`py::exec()`関数に文字列でPythonコードを与えるとそのまま実行してくれます。
`py::eval()`とは違って、こちらは改行で区切って複数のコマンドを実行したり、クラスを定義したりすることもできます。

```cpp
py::exec(
    "print('Hello!')\n"
    "print('World!')", global);
```
```:実行結果
Hello!
World!
```

新しく変数を作成した場合、第2引数に与えた名前空間上に生成されます。

```cpp
// Pythonのグローバル名前空間内のresult変数に2**3を代入
py::exec("result = 2**3", global);

// 結果をint型に変換して出力
std::cout << py::extract<int>(global["result"]) << std::endl;
```
```:実行結果
8
```

### ■ `py::exec_file()`: Pythonコードをファイルから実行
`py::exec_file()`を使用すると、ファイルの内容を読み込んで`py::exec()`と同じように実行できます。
第1引数にはPythonスクリプトへのファイルパスを指定します。

```cpp
py::exec_file("hello.py", global);
```

自作クラスの定義を、C++とは別にPythonスクリプトファイルとして用意しておく場合に便利です。

### ■ `py::object::attr()`: オブジェクトのフィールド/メソッドの参照
`py::object`のフィールド(メンバ変数)やメソッド(メンバ関数)は、`attr()`関数を使用することで使用できます。

```cpp:str型のjoinメソッドを呼び出す例
// ['hoge','fuga','piyo']というリストを作成
py::object list = py::eval("['hoge','fuga','piyo']", global);

// 「','.join(list)」を実行し、リストの文字列をカンマ区切りで結合
py::object result = py::object(",").attr("join")(list);

// 結果を文字列型に変換して出力
std::string resultStr = py::extract<std::string>(result);
std::cout << resultStr << std::endl;
```
```:実行結果
hoge,fuga,piyo
```

### ■ `py::import()`: Pythonライブラリの使用
`py::import`を使用することで、Pythonの`import`文と同様にライブラリをインポートできます。

```cpp
py::object np = py::import("numpy");
```

例として、以下のPythonコードを`py::exec()`を使用せずにC++で書いてみることにします。
matplotlibでy=x^2のグラフを表示するサンプルです。

```python:Pythonコードのサンプル
import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-10, 10, 0.1)
y = x ** 2
plt.plot(x, y)
plt.show()
```

```cpp:上記をBoost.Pythonを使用してC++で書いたサンプル
py::object np = py::import("numpy");
py::object plt = py::import("matplotlib.pyplot");

py::object x = np.attr("arange")(-10, 10, 0.1);
py::object y = x * x;
plt.attr("plot")(x, y);
plt.attr("show")();
```
(C++側では`x ** 2`がそのまま書けないので、代わりに`x * x`にしています)

実行すると、以下のようにmatplotlibでグラフが表示されます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192805/75ee2ab8-2b94-8375-d176-978f4c024a1e.png" alt="y=x^2のグラフ" width="360px">

# 参考

* 公式サイトのチュートリアル:
    * https://www.boost.org/doc/libs/1_62_0/libs/python/doc/html/tutorial/tutorial/embedding.html
    * 割と情報が乏しいです…
* 公式リポジトリ内のサンプルプログラム:
    * [https://github.com/boostorg/python/blob/.../example/quickstart/embedding.cpp](https://github.com/boostorg/python/blob/ac62db1cf17c0af44dc2b2117f9ddeee327e5e7c/example/quickstart/embedding.cpp)
    * 本記事で紹介していない、PythonクラスのC++ラッパーを作って`py::extract()`で変換する例などが載っています