C/C++で何もライブラリを使用せずにpythonを動かしてみる。
久しぶりの更新。今回は結構特大記事。
環境
OS: ubuntu 20.10
CPU: Intel(R) Core(TM) i7-10510U CPU
GPU: CPU内蔵
python: 3.8.6
tensorflow: 2.3.1
keras: tensorflow内蔵
どーでもいい情報入っているかもしれんが定型文だからしょうがない()
経緯
この頃、C++をいじってるのだけれども、自分はC++はほぼかけないし(読めるけど)書き方がちょっとあれ*1だったし、何よりもkeras(tensorflow)使いたいしということで、C++でpythonを動かすことにした。
C++使わずにpythonを使えばいいじゃん。と考える人がいそうなので言っておくと、ベースのC++があって、それを改造している状況なのだ。そのプログラムが結構大きいので自力で変更するのは無限に時間がかかりそうだからC++でpythonを使ってやろうと思っているのだ。
やり方を調べてみる。
まず、自分が知っているのは、boostを使用してpythonを実行する方法。boostは今回使うプログラムでも使ってるのでいいかも。
でも、boostで動かす方法はあまり情報がなく断念。
次は、Cython*2を使用してやる方法だ。しかし、実際にやってみると、pythonが実行できるわけでわなくC/C++しか実行できず、pythonを書くと、セグメンテーション違反で止まることが発覚したのでこれもできず。
最後は、python標準でついているC APIを使用して、実行することができるし、情報もwikiが日本語でしっかりしてるのでこれがいいかも。
早速やってみる。
テストプログラム
#include <Python.h> int main() { Py_Initialize(); PyRun_SimpleString("print('test')"); //ここにpythonプログラムを書く。 Py_Finalize(); return 0; }
Py_Initialize
でpythonを初期化、Py_Finalize
でリセットしてメモリを開放する。そして、その間にpythonに関するプログラムを記入する。
詳しくは
そして、PyRun_SimpleString
関数でpythonプログラムを実行できる。改行は\n
で可能。また、今回はやらないが、ファイルから読み込みなどいろいろな方法がある。詳しくは
ビルド
ここではコンソールでやる場合を説明する。
まず、Python.h
を読み込ませなくてはいけない。でも、環境によって場所が違うのでpkg-configを使用する。Autotoolsやautomakeにはべんりな関数があるので使用するうとよい。
qiita.com
pythonが入っている場合は以下のコマンドで引数が返ってくる。
pkg-config python3 --cflags # -I/usr/include/python3.8 -I/usr/include/x86_64-linux-gnu/python3.8
なので、以下のコマンドで読み込みができる。
gcc main.cpp `pkg-config python3 --cflags`
しかし、このままではライブラリがなくてエラーが吐かれてしまうので、ライブラリを追加する。(インストールは不要)
gcc main.cpp `pkg-config python3 --cflags` -lpython3.8
-python3.8
はインストールされているpythonのバージョンに変更してください。
これで、ビルドができて、a.out
というファイルができる。
実行すると、
$ ./a.out
test
このように実行ができた。
おまけ
別の関数でpythonを使用したい場合
別の関数でpythonを使用したい場合は以下のように、Py_Initialize
とPy_Finalize
でちゃんと囲まれていたらたとえファイルが違えども大丈夫。
// ------- main.cpp ------- #include <Python.h> #include "test.h" void hogehoge() { PyRun_SimpleString("print('このプログラムはhogehoge関数で実行しています。')"); } int main() { Py_Initialize(); PyRun_SimpleString("print('このプログラムはmain関数で実行しています。')"); hogehoge(); test_func(); Py_Finalize(); return 0; } // ------- test.cpp ------- #include <Python.h> void test_func() { PyRun_SimpleString("print('このプログラムはtestファイル内のtest_func関数で実行しています。')"); } // ------- test.h ------- void test_func();
$ gcc main.cpp test.cpp `pkg-config python3 --cflags` -lpython3.8 $ ./a.out このプログラムはmain関数で実行しています。 このプログラムはhogehoge関数で実行しています。 このプログラムはtestファイル内のtest_func関数で実行しています。
importしたpythonライブラリの引き継ぎについて
これに関しても、ちゃんと囲まれてたら1度呼び出したら、どこでも使える。
テストコード
// ------- main.cpp ------- #include <Python.h> #include "test.h" void hogehoge() { PyRun_SimpleString("time.sleep(1)\n" "print('このプログラムはhogehoge関数で実行しています。',datetime.datetime.now())"); } int main() { Py_Initialize(); PyRun_SimpleString("import time, datetime"); PyRun_SimpleString("time.sleep(1)\n" "print('このプログラムはmain関数で実行しています。',datetime.datetime.now())"); hogehoge(); test_func(); Py_Finalize(); return 0; } // ------- test.cpp ------- #include <Python.h> void test_func() { PyRun_SimpleString("time.sleep(1)\n" "print('このプログラムはtestファイル内のtest_func関数で実行しています。',datetime.datetime.now())" ); } // ------- test.h ------- void test_func();
$ gcc main.cpp test.cpp `pkg-config python3 --cflags` -lpython3.8 $ ./a.out このプログラムはmain関数で実行しています。 2020-12-06 16:19:45.493962 このプログラムはhogehoge関数で実行しています。 2020-12-06 16:19:46.495139 このプログラムはtestファイル内のtest_func関数で実行しています。 2020-12-06 16:19:47.496891
ちゃんと、メッセージあとの時間が違う。
エラーについて
ちゃんとエラーは出してくれる。PyRun_SimpleString
の場合だと、ファイル名は<string>
になる。
プログラムは停止せず実行される。
先程のmain関数を変更。
int main() { Py_Initialize(); PyRun_SimpleString("import time, datetime"); PyRun_SimpleString("time.sleep(1)\n" "print('このプログラムはmain関数で実行しています。',datetime.datetime.now())\n" "raise ValueError"); hogehoge(); test_func(); Py_Finalize(); return 0; }
$ gcc main.cpp test.cpp `pkg-config python3 --cflags` -lpython3.8 $ ./a.out このプログラムはmain関数で実行しています。 2020-12-06 17:35:03.561291 Traceback (most recent call last): File "<string>", line 3, in <module> ValueError このプログラムはhogehoge関数で実行しています。 2020-12-06 17:35:04.778844 このプログラムはtestファイル内のtest_func関数で実行しています。 2020-12-06 17:35:05.780182
別の環境でも実行したい場合
最小環境でも動かせるようにやってみる。今回はdockerを使用した。前提として、gccやmake等のビルドに必要なライブラリが入っていることを前提とする。
やり方としては、libフォルダにpythonを全部入れてその状態でパスを通してあげてビルドをする。
zlibのライブラリを持ってくる。
pythonのコンパイル・インストールにはzlib
が必要なのでzlib
をコンパイル・インストールする。
wget https://zlib.net/zlib-1.2.11.tar.gz tar xzvf zlib-1.2.11.tar.gz cd zlib-1.2.11/ ./configure --prefix ~/lib/zlib/ make make install
configure
で指定している--prefix ~/lib/
でインストール場所を指定している。環境によってパスは変更してください。zlib
ディレクトリ内のshare
ディレクトリはmanページファイルしか入っていないので削除しても問題はない。
pythonを入れる。
Python Source Releases | Python.org
ここからソースコードのリンクをコピーする。 自分は、3.8.6のgzip形式を使用する。
wget https://www.python.org/ftp/python/3.8.4/Python-3.8.4.tgz tar -xzvf Python-3.8.4.tgz cd Python-3.8.4/ export CPPFLAGS='-I/root/lib/zlib/include/' export LDFLAGS='-L/root/lib/zlib/lib/' ./configure --prefix ~/lib/python3 --enable-shared make make install
注意
今回はpythonがコンパイルできればいいのでpython環境も最小構成だ。なので、make
が終わってから以下のwarningが出る。
_bz2 _curses _curses_panel _dbm _gdbm _hashlib _lzma _sqlite3 _ssl _tkinter _uuid readline To find the necessary bits, look in setup.py in detect_modules() for the module's name. The following modules found by detect_modules() in setup.py, have been built by the Makefile instead, as configured by the Setup files: _abc atexit pwd time Failed to build these modules: _ctypes
書いてあるとおり、いろいろ必要なライブラリがないらしい。今回は使わないからスルー。全部入れたいときは、いろいろなサイトに書かれてあるので各自確認してください。
注意終わり
こちらも、configure
で指定している--prefix ~/lib/
でインストール場所を指定している。環境によってパスは変更してください。同じようにpython3
ディレクトリ内のshare
ディレクトリはmanページファイルしか入っていないので削除しても問題はない。--enable-shared
は、コンパイル後のディレクトリ内のlib
ディレクトリ内に共有ライブラリを生成する引数。
変数のCPPFLAGS
とLDFLAGS
はそれぞれzlib
のinclude
とlib
の絶対パスを代入する。
ビルド
pkg-config python3 --cflags
は./lib/python3/include/python3.8
にあたる(環境によって違う)のでこのように改造。
gcc main.cpp test.cpp -I./lib/python3/include/python3.8 -lpython3.8
しかし、このままでは共有ライブラリの場所がわからなくなるので、パスを-L
で追加する。
ライブラリの場所は./lib/python3/lib/
にある。
gcc main.cpp test.cpp -I./python3/include/python3.8 -L./lib/python3/lib/ -lpython3.8
最終的にはこうなる。
実行
実行すると、以下のエラーが発生する。
./a.out: error while loading shared libraries: libpython3.8.so.1.0: cannot open shared object file: No such file or directory
なので、ライブラリのパスを環境変数で与えてやる。(同じ環境変数を使用している場合は、なんかいい感じに変えてください)
LD_LIBRARY_PATH=./lib/python3/lib/
で渡している。
# LD_LIBRARY_PATH=./lib/python3/lib/ ./a.out このプログラムはmain関数で実行しています。 2020-12-06 11:21:24.547966 Traceback (most recent call last): File "<string>", line 3, in <module> ValueError このプログラムはhogehoge関数で実行しています。 2020-12-06 11:21:25.549383 このプログラムはtestファイル内のtest_func関数で実行しています。 2020-12-06 11:21:26.550680
ちゃんと実行できた。
最後に
実行してるときの時刻見てもらえるとわかるけど、めっちゃ苦戦してる。これを実現するために、1週間くらいかかった。誰かの役に立ってくれれば泣いて喜びますので宜しくおねがいします!!!!
個人的な質問等はこちらまで。
https://forms.gle/V6NRhoTooFw15hJdA
また、自分が参加しているRobocup soccer シミュレーションリーグのチームでは参加者募集中です!活動の見学、活動に参加したい方、ご連絡お待ちしております!
詳しくはこちら