1. m4saka

    No comment

    m4saka
Changes in body
Source | HTML | Preview

追記: 乱用注意!

むやみやたらにC++にPythonのコードを埋め込むことは考えない方が良いです。
コードが複雑化するので、基本的にはC++用のライブラリを使う、またはPythonでコードを完結させることをお勧めします。
(例えば、OpenCVやPytorchはC++から直接扱えますし、行列計算もnumpyの代わりにC++向け行列ライブラリのEigenを使えば良いでしょう。)

この記事の方法は、どうしても必要な場合のみ使用することをお勧めします。

はじめに

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

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

最もシンプルなコード例

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の場合、以下のコマンドでコンパイルできます。

コンパイル方法
$ 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**の部分がライブラリ名です。

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

基本的な使い方

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

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

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

py::eval(): Pythonで式を評価し、結果を返す

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

// "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()とは違って、こちらは改行で区切って複数のコマンドを実行したり、クラスを定義したりすることもできます。

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

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

// 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スクリプトへのファイルパスを指定します。

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

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

py::object::attr(): オブジェクトのフィールド/メソッドの参照

py::objectのフィールド(メンバ変数)やメソッド(メンバ関数)は、attr()関数を使用することで使用できます。

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文と同様にライブラリをインポートできます。

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

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

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()
上記を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でグラフが表示されます。
y=x^2のグラフ

参考