追記: 乱用注意!
あなたがもし、面倒な処理を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を埋め込む場合の記事がヒットしやすいです。
最もシンプルなコード例
#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
の**
の部分がライブラリ名です。
$ 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()
関数を使用することで使用できます。
// ['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のグラフを表示するサンプルです。
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()
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でグラフが表示されます。
参考
- 公式サイトのチュートリアル:
- 公式リポジトリ内のサンプルプログラム:
- https://github.com/boostorg/python/blob/.../example/quickstart/embedding.cpp
- 本記事で紹介していない、PythonクラスのC++ラッパーを作って
py::extract()
で変換する例などが載っています