他のアプリケーションへの Python の埋め込み

前章では、 Python を拡張する方法、すなわち C 関数のライブラリを Python に結びつけて機能を拡張する方法について述べました。同じようなことを別の方法でも実行できます: それは、自分の C/C++ アプリケーションに Python を埋め込んで機能を強化する、というものです。埋め込みを行うことで、アプリケーションの何らかの機能を C や C++ の代わりに Python で実装できるようになります。埋め込みは多くの用途で利用できます; ユーザが Python でスクリプトを書き、アプリケーションを自分好みに仕立てられるようにする、というのがその一例です。プログラマが、特定の機能を Python でより楽に書ける場合に自分自身のために埋め込みを行うこともできます。

Python の埋め込みは Python の拡張と似ていますが、全く同じというわけではありません。その違いは、Python を拡張した場合にはアプリケーションのメインプログラムは依然として Python インタプリタである一方、 Python を組み込みんだ場合には、メインプログラムには Python が関係しない --- その代わりに、アプリケーションのある一部分が時折 Python インタプリタを呼び出して何らかの Python コードを実行させる --- かもしれない、ということです。

従って、 Python の埋め込みを行う場合、自作のメインプログラムを提供しなければなりません。メインプログラムがやらなければならないことの一つに、 Python インタプリタの初期化があります。とにかく少なくとも関数 Py_Initialize() を呼び出さねばなりません。オプションとして、Python 側にコマンドライン引数を渡すために関数呼び出しを行います。その後、アプリケーションのどこでもインタプリタを呼び出せるようになります。

インタプリタを呼び出すには、異なるいくつかの方法があります: Python 文が入った文字列を PyRun_SimpleString() に渡す、 stdio ファイルポインタとファイル名 (これはエラーメッセージ内でコードを識別するためだけのものです) を PyRun_SimpleFile() に渡す、といった具合です。これまでの各章で説明した低水準の操作を呼び出して、Python オブジェクトを構築したり使用したりもできます。

参考

Python/C API リファレンスマニュアル

Python C インターフェースの詳細はこのマニュアルに書かれています。必要な情報の大部分はここにあるはずです。

高水準の埋め込み

Python の埋め込みの最も簡単な形式は、超高水準インターフェースの利用です。このインターフェースは、アプリケーションとやり取りする必要がない Python スクリプトを実行するためのものです。例えばこれは、一つのファイル上で何らかの操作を実現するのに利用できます。

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    PyStatus status;
    PyConfig config;
    PyConfig_InitPythonConfig(&config);

    /* 省略可能だが推奨 */
    status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
    if (PyStatus_Exception(status)) {
        goto exception;
    }

    status = Py_InitializeFromConfig(&config);
    if (PyStatus_Exception(status)) {
        goto exception;
    }
    PyConfig_Clear(&config);

    PyRun_SimpleString("from time import time,ctime\n"
                       "print('Today is', ctime(time()))\n");
    if (Py_FinalizeEx() < 0) {
        exit(120);
    }
    return 0;

  exception:
     PyConfig_Clear(&config);
     Py_ExitStatusException(status);
}

注釈

#define PY_SSIZE_T_CLEAN は、いくつかの API では int の代わりに Py_ssize_t が使われるべきであることを示すために使われていました。これは Python 3.13 以降では不要となりましたが、後方互換性のために維持されています。このマクロの説明は 文字列とバッファ にあります。

Setting PyConfig.program_name should be called before Py_InitializeFromConfig() to inform the interpreter about paths to Python run-time libraries. Next, the Python interpreter is initialized with Py_Initialize(), followed by the execution of a hard-coded Python script that prints the date and time. Afterwards, the Py_FinalizeEx() call shuts the interpreter down, followed by the end of the program. In a real program, you may want to get the Python script from another source, perhaps a text-editor routine, a file, or a database. Getting the Python code from a file can better be done by using the PyRun_SimpleFile() function, which saves you the trouble of allocating memory space and loading the file contents.

超高水準の埋め込みから踏み出す: 概要

高水準インターフェースは、断片的な Python コードをアプリケーションから実行できるようにしてくれますが、アプリケーションと Python コードの間でのデータのやり取りは、控えめに言っても煩わしいものです。データのやり取りをしたいなら、より低水準のインターフェース呼び出しを利用しなくてはなりません。より多く C コードを書かねばならない代わりに、ほぼ何でもできるようになります。

Python の拡張と埋め込みは、趣旨こそ違え、同じ作業であるということに注意せねばなりません。これまでの章で議論してきたトピックのほとんどが埋め込みでもあてはまります。これを示すために、 Python から C への拡張を行うコードが実際には何をするか考えてみましょう:

  1. データ値を Python から C に変換する。

  2. 変換された値を使って C ルーチンの関数呼び出しを行い、

  3. 呼び出しで得られたデータ値 C から Python に変換する。

Python を埋め込む場合には、インターフェースコードが行う作業は以下のようになります:

  1. データ値を C から Python に変換する。

  2. 変換された値を使って Python インターフェースルーチンの関数呼び出しを行い、

  3. 呼び出しで得られたデータ値 Python から C に変換する。

一見して分かるように、データ変換のステップは、言語間でデータを転送する方向が変わったのに合わせて単に入れ替えただけです。唯一の相違点は、データ変換の間にあるルーチンです。拡張を行う際には C ルーチンを呼び出しますが、埋め込みの際には Python ルーチンを呼び出します。

この章では、Python から C へ、そしてその逆へとデータを変換する方法については議論しません。また、正しい参照の使い方やエラーの扱い方についてすでに理解しているものと仮定します。これらの側面についてはインタプリタの拡張と何ら変わるところがないので、必要な情報については以前の章を参照できます。

純粋な埋め込み

最初に例示するプログラムは、Python スクリプト内の関数を実行するためのものです。超高水準インターフェースに関する節で挙げた例と同様に、Python インタプリタはアプリケーションと直接やりとりはしません (が、次の節でやりとりするよう変更します)。

Python スクリプト内で定義されている関数を実行するためのコードは以下のようになります:

#define PY_SSIZE_T_CLEAN
#include <Python.h>

int
main(int argc, char *argv[])
{
    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pValue;
    int i;

    if (argc < 3) {
        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
        return 1;
    }

    Py_Initialize();
    pName = PyUnicode_DecodeFSDefault(argv[1]);
    /* pName のエラーチェックは省略 */

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        /* pFunc は新しい参照 */

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(argc - 3);
            for (i = 0; i < argc - 3; ++i) {
                pValue = PyLong_FromLong(atoi(argv[i + 3]));
                if (!pValue) {
                    Py_DECREF(pArgs);
                    Py_DECREF(pModule);
                    fprintf(stderr, "Cannot convert argument\n");
                    return 1;
                }
                /* ここで pValue の参照が盗まれる: */
                PyTuple_SetItem(pArgs, i, pValue);
            }
            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);
            if (pValue != NULL) {
                printf("Result of call: %ld\n", PyLong_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"Call failed\n");
                return 1;
            }
        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
        return 1;
    }
    if (Py_FinalizeEx() < 0) {
        return 120;
    }
    return 0;
}

このコードは argv[1] を使って Python スクリプトをロードし、 argv[2] 内に指定された名前の関数を呼び出します。関数の整数引数は argv 配列中の他の値になります。このプログラムを コンパイルしてリンク し (できた実行可能形式を call と呼びましょう)、以下のような Python スクリプトを実行することにします:

def multiply(a,b):
    print("Will compute", a, "times", b)
    c = 0
    for i in range(0, a):
        c = c + b
    return c

実行結果は以下のようになるはずです:

$ call multiply multiply 3 2
Will compute 3 times 2
Result of call: 6

機能の割に大きなプログラムですが、コードの大部分は Python と C の間のデータ変換と、エラーの報告のためのものです。 Python の埋め込みという観点から重要なのは、次の箇所から始まる処理です。

Py_Initialize();
pName = PyUnicode_DecodeFSDefault(argv[1]);
/* pName のエラーチェックは省略 */
pModule = PyImport_Import(pName);

インタープリタを初期化した後、PyImport_Import() でスクリプトをロードします。 この関数には Python の文字列を引数として渡す必要があるため、データ変換関数 PyUnicode_DecodeFSDefault() で作成します。

pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc は新しい参照 */

if (pFunc && PyCallable_Check(pFunc)) {
    ...
}
Py_XDECREF(pFunc);

ひとたびスクリプトが読み込まれると、 PyObject_GetAttrString() を使って必要な名前を取得できます。名前がスクリプト中に存在し、取得したオブジェクトが呼び出し可能オブジェクトであれば、このオブジェクトが関数であると考えて差し支えないでしょう。そこでプログラムは定石どおりに引数のタプル構築に進みます。その後、Python 関数を以下のコードで呼び出します:

pValue = PyObject_CallObject(pFunc, pArgs);

関数が処理を戻す際、 pValue は NULL になるか、関数の戻り値への参照が入っています。値を調べた後には忘れずに参照を解放してください。

埋め込まれた Python の拡張

ここまでは、埋め込み Python インタプリタはアプリケーション本体の機能にアクセスする手段がありませんでした。 Python API を使うと、埋め込みインタプリタを拡張することでアプリケーション本体へのアクセスを可能にします。つまり、アプリケーションで提供されているルーチンを使って、埋め込みインタプリタを拡張するのです。複雑なことのように思えますが、それほどひどいわけではありません。さしあたって、アプリケーションが Python インタプリタを起動したということをちょっと忘れてみてください。その代わり、アプリケーションがサブルーチンの集まりで、あたかも普通の Python 拡張モジュールを書くかのように、Python から各ルーチンにアクセスできるようにするグルー(glue, 糊) コードを書くと考えてください。例えば以下のようにです:

static int numargs=0;

/* アプリケーションのコマンドライン引数の数を返す */
static PyObject*
emb_numargs(PyObject *self, PyObject *args)
{
    if(!PyArg_ParseTuple(args, ":numargs"))
        return NULL;
    return PyLong_FromLong(numargs);
}

static PyMethodDef emb_module_methods[] = {
    {"numargs", emb_numargs, METH_VARARGS,
     "プロセスが受け取った引数の数を返します。"},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef emb_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "emb",
    .m_size = 0,
    .m_methods = emb_module_methods,
};

static PyObject*
PyInit_emb(void)
{
    return PyModuleDef_Init(&emb_module);
}

上のコードを main() 関数のすぐ上に挿入します。また、以下の二つの文を Py_Initialize() の呼び出しの前に挿入します:

numargs = argc;
PyImport_AppendInittab("emb", &PyInit_emb);

この 2 行は、numargs 変数を初期化し、埋め込まれた Python インタープリタから emb.numargs() 関数を利用できるようにします。 この拡張によって、Python スクリプトで次のような処理ができます。

import emb
print("Number of arguments", emb.numargs())

実際のアプリケーションでは、こうしたメソッドでアプリケーション内の API を Python に公開することになります。

C++による Python の埋め込み

C++ プログラム中にも Python を埋め込めます; 厳密に言うと、どうやって埋め込むかは使っているC++ 処理系の詳細に依存します; 一般的には、メインプログラムをC++で書き、C++ コンパイラを使ってプログラムをコンパイル・リンクする必要があるでしょう。 Python 自体を C++でコンパイルしなおす必要はありません。

Unix 系システムにおけるコンパイルとリンク

Python インタープリタをアプリケーションに組み込むために、コンパイラとリンカーに渡す適切なフラグを見つけるのは、必ずしも簡単ではありません。 特に、Python にリンクされた C の動的拡張 (.so ファイル) として実装されたライブラリモジュールを、Python がロードする必要があるためです。

必要なコンパイル・リンクのオプションを知るために、 pythonX.Y-config スクリプトが使えます(これは Python インストール時に生成されたもので、 python3-config スクリプトも利用出来るかもしれません)。このスクリプトにはオプションが多くありますが、直接的に有用なのはこれでしょう:

  • pythonX.Y-config --cflags は推奨のコンパイルオプションを出力します:

    $ /opt/bin/python3.11-config --cflags
    -I/opt/include/python3.11 -I/opt/include/python3.11 -Wsign-compare  -DNDEBUG -g -fwrapv -O3 -Wall
    
  • pythonX.Y-config --ldflags --embed は推奨のリンクオプションを出力します:

    $ /opt/bin/python3.11-config --ldflags --embed
    -L/opt/lib/python3.11/config-3.11-x86_64-linux-gnu -L/opt/lib -lpython3.11 -lpthread -ldl  -lutil -lm
    

注釈

複数 Python バージョン共存(とりわけシステムの Python とあなた自身でビルドした Python)での混乱を避けるために、上での例のように pythonX.Y-config は絶対パスで起動したほうが良いです。

この手順でうまくいかない場合は、システムの動的リンクに関するドキュメントを読むか、Python の Makefile とコンパイルオプションを調べる、あるいはその両方を行う必要があります。 すべての Unix 系プラットフォームで動作する保証はありませんが、バグ報告 は歓迎します。 Makefile の場所は sysconfig.get_makefile_filename() で調べられます。 この場合、sysconfig モジュールを使うと、組み合わせたい設定値をプログラムで抽出できて便利です。 例えば、次のように使います。

>>> import sysconfig
>>> sysconfig.get_config_var('LIBS')
'-lpthread -ldl  -lutil'
>>> sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'