今までの連載を通じて、scikit-imageにある例題(注2)を、
そのまま動かすこと、
入力ファイルを変えること、
複数の入力ファイルを扱うこと
結果をファイルに書き込むこと
GUIを使うこと
ネットワークと連携すること
コードを改善して、モジュールとして再利用すること
処理時間を計測することとプロファイルすることを示した。
そこまで、使いこなしてくると、次はそれをC/C++言語から利用したくなってきているに違いない。
そこで今回はC/C++言語から、Pythonを利用する方法を紹介する。
C/C++言語からPythonを利用する
###1.system関数、popen関数の利用
pythonスクリプトをsystem()関数で
system("python myscript.py");
とすることで、C言語からPythonを利用できる。
fp=popen("python myscript.py","r");
として、実行するだけではなく、その結果を、受け取ることができる。
とりあえずこの方法は、C言語さえ使えれば、難しいことなしに実行できる。
この方法の課題:
処理を実行するたびに、systemやpopenでpythonインタプリタを起動しまくるので、そのオーバーヘッドが生じてしまう。しかも実行するPythonスクリプトが、処理の準備のために学習済みのファイルを読みとることが必要になる場合には、オーバーヘッドがとても大きくなってしまう。
###2.他のアプリケーションへの Python の埋め込み
Python 標準ドキュメント[他のアプリケーションへの Python の埋め込み]
(http://docs.python.jp/2/extending/embedding.html)
Python 標準ドキュメント5.3. 純粋な埋め込み
プログラムは、Python スクリプト内の関数を実行するためのものです。
このやり方では、余分なプロセスを発生させることがない。
埋め込まれたPython部分に、引数を渡して、その結果を受け取ることができる。
PyImport_Import(pName)
で読み取っているモジュールの名前は、
この例題では、ここにあるpythonモジュールの名前を与えて、そのモジュールに含まれる関数を実行している。
(正確には、Pythonがimportすべきモジュール名を指定して、そのPYTHONPATHの設定にしたがって、カレントディレクトリにあるモジュール(.py, .pyc, .pydだったりする)を呼んでいるようだ。
この例題の引数に"$ call ../multiply multiply 3 2 などというディレクトリ構造を含めてしまうと、
PyImport_Import()で失敗してしまう。)
モジュールに含める関数を増やして
次のようにすることができる。
def multiply(a,b):
print "Will compute", a, "times", b
c = 0
for i in range(0, a):
c = c + b
return c
def add(a,b):
print "Will compute", a, " plus ", b
return a+b
def div(a,b):
print "Will compute", a, " div ", b
return a/b
実行結果
>call.exe intmath div 5 2
Will compute 5 div 2
Result of call: 2
2
>call.exe intmath multiply 6 3
Will compute 6 times 3
Result of call: 18
18
>call.exe intmath add 6 3
Will compute 6 plus 3
Result of call: 9
9
>
のようにすることもできる。
この例題は、コマンドラインから入力される値をチェックしていないので、とても危険である。
次のようにしてPythonのオブジェクトを、C言語のデータ構造に変換することができる。
long PyInt_AsLong(PyObject *io)
http://docs.python.jp/2/c-api/int.html
double PyFloat_AsDouble(PyObject *pyfloat)
http://docs.python.jp/2/c-api/float.html
このようにして埋め込んでC言語から利用すると、system()関数やpopen()関数で、pythonインタプリタを起動して終了を繰り返すオーバーヘッドをなくすことができる。なお、
Py_Initialize();
pModule = PyImport_Import(pName);
Py_Finalize();
は、本当に必要なタイミングでだけ実行するようにしておくのも重要だ。
効果:
C/C++言語側は、呼び出すモジュールと関数と引数と戻り値というインタフェースさえ変わらなければ、Pythonモジュールの実装がどのように変化しても、ビルドしなおす必要が一切ないという利点がある。
まだまだ勉強中[注1]。
5.3. 純粋な埋め込みの例題のプログラムでは
関数の引数、戻り値が整数型なので、それらを浮動小数点に置き換えるだけのプログラムを作ってみよう。
埋め込みのプログラムを書くのは、Python、C言語の両方についてそれなりの理解をしていることが必要で、動かしてやるんだという執念が必要かもしれない。身近にPython使いがいるからといって、Pythonの埋め込みを使いこなせるPythonプログラマが身近にいるというのは、かなり珍しい状況だろう。
だから、ごくわずかだけ書き換えたプログラムを書いてみて、まねてみることをしてみよう。
###追記:Debugモードでビルドできないとき
LINK : fatal error LNK1104: cannot open file 'python27_d.lib'
というメッセージがでることがVisualStudioであります。
次のようにしてReleaseモードとDebugモードでともにpython27.libを利用するやり方が
stackoverflow
に書いてありました。
#ifdef _DEBUG
#undef _DEBUG
#include <Python.h>
#define _DEBUG
#else
#include <Python.h>
#endif
注:Pythonを埋め込んで利用しているときに、タスクマネジャーでプロセスを表示させてみた。Pythonモジュール部分を実行させてみても、python.exeのプロセスが生成されていないことが確認できた。Pythonの起動、スクリプトの中の初期設定などを何度も繰り返すことがなくなるので、systemコマンドやpopenを利用している人は、ぜひ埋め込みを試してみてください。
書かなくてはならないことなど
Boost.Python
Scipy.WeaveでPythonにC++埋め込み
Cython
Numpy C-API
Scipy Python Types and C-Structures
注1:C/C++でリンクするライブラリを増やしていくと、「このライブラリがあのライブラリを必要として、そのライブラリはさらに何とかというライブラリを必要として、それをリンクしないと、今まであった部分のビルドさえできなくなってしまう」
という悲しい事態になりがちです。
C/C++言語へのPythonの埋め込みの場合には、リンクされるのは、ここで書いた
#include <Python.h>
の行を含むモジュールです。
この部分がC/C++言語とPython言語との橋立ちをしていて、
それ以外のPython言語の部分は、リンクはされていない。
だからこそ、例題のCプログラムの中でimportするモジュール名をコマンドライン引数で与えることができます。
Pythonのインタプリタと使用する範囲のpythonのライブラリがインストールされていれば十分であって、
「このモジュール、あのモジュール、さらにどのモジュール」などという
リンクすべき対象が際限なく増えているのを予防できるというのもうれしい点です。
注:WindowsのVisual Studioの場合には、コンパイラのバージョンが変わるとリンクできないという仕様になっています。
このことは、OpenCVのような巨大なライブラリをビルドするときには、やっかいなことになってきます。Python用のライブラリをC/C++拡張、Cython言語から生成する際にも、Visual Studioのバージョンをそろえなくてはならない。
注2:Scikit-imageにある例題を選んだ理由は、例題を実行させるときに、「必要なデータをどこから入手したらいいの?」という問題を生じないからです。