LoginSignup
5
5

More than 3 years have passed since last update.

C/C++で何もライブラリを使用せずにpythonを動かしてみる。

Posted at

はじめに

いつもははてなブログで投稿している@kumitatepazuruです。qiitaのアドベントカレンダーが空いていたので投稿してみました。

元記事:https://kumitatepazuru.hatenablog.com/entry/20201207/1607294720

環境

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++はほぼかけないし(読めるけど)書き方がちょっとあれ(https://clown.cube-soft.jp/entry/20090708/p1 )だったし、何よりもkeras(tensorflow)使いたいしということで、C++でpythonを動かすことにした。

C++使わずにpythonを使えばいいじゃん。と考える人がいそうなので言っておくと、ベースのC++があって、それを改造している状況なのだ。そのプログラムが結構大きいので自力で変更するのは無限に時間がかかりそうだからC++でpythonを使ってやろうと思っているのだ。

やり方を調べてみる。

まず、自分が知っているのは、boostを使用してpythonを実行する方法。boostは今回使うプログラムでも使ってるのでいいかも。

でも、boostで動かす方法はあまり情報がなく断念。

次は、Cython(https://qiita.com/pashango2/items/45cb85390193d97523ca )を使用してやる方法だ。しかし、実際にやってみると、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にはべんりな関数があるので使用するうとよい。
https://qiita.com/tomoyuki-nakabayashi/items/cab28ff7ccd717ae89cd#dive-into-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB
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_InitializePy_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を入れる。

[https://www.python.org/downloads/source/:title]

ここからソースコードのリンクをコピーする。
自分は、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が出る。

The necessary bits to build these optional modules were not found:
_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ディレクトリ内に共有ライブラリを生成する引数。

変数のCPPFLAGSLDFLAGSはそれぞれzlibincludelib絶対パスを代入する。

ビルド

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週間くらいかかった。誰かの役に立ってくれれば泣いて喜びますので宜しくおねがいします!!!!

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5