工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Industrial programming >> Python

Python C拡張プログラミング:高速ネイティブモジュールの構築

拡張モジュールの概要

C、C++、Javaなどのコンパイル言語で記述したコードは、別のPythonスクリプトに統合またはインポートできます。このコードは「拡張」と呼ばれます。

Python拡張モジュールは、通常のCライブラリに過ぎません。Unixマシンでは通常.so(共有オブジェクト)で終わり、Windowsマシンでは通常.dll(動的リンクライブラリ)を見かけます。

拡張記述の前提条件

拡張の記述を開始するには、Pythonヘッダーファイルが必要です。

また、CまたはC++の十分な知識があり、Cプログラミングを使用してPython拡張を記述できることが前提とされます。

Python拡張の初めての見た目

初めてPython拡張モジュールを見るには、コードを以下の4つの部分にグループ化する必要があります。

ヘッダーファイルPython.h

CソースファイルにPython.hヘッダーファイルをインクルードする必要があります。これにより、モジュールをインタープリターにフックするために使用される内部Python APIへのアクセスが得られます。

他のヘッダーが必要な場合よりも前にPython.hをインクルードしてください。インクルードの後にPythonから呼び出したい関数を続けます。

C関数

関数のC実装のシグネチャは、常に以下の3つの形式のいずれかを取ります。

static PyObject *MyFunction(PyObject *self, PyObject *args);
static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *MyFunctionWithNoArgs(PyObject *self);

上記の宣言のそれぞれはPythonオブジェクトを返します。Cにあるようなvoid関数というものはPythonには存在しません。関数に値を返させたくない場合は、PythonのNoneに相当するCの値を返します。Pythonヘッダーは、これを行うPy_RETURN_NONEマクロを定義しています。

C関数の名前は何でも構いません。拡張モジュールの外部からは決して見えないためです。static関数として定義されます。

通常、C関数名はPythonモジュール名と関数名を組み合わせて命名します。

static PyObject *module_func(PyObject *self, PyObject *args) {
    /* ここで処理を実行 */
    Py_RETURN_NONE;
}

これは、モジュールmodule内のfuncというPython関数です。このC関数へのポインタを、通常ソースコードの次に来るモジュールのメソッドテーブルに格納します。

メソッドマッピングテーブル

このメソッドテーブルは、PyMethodDef構造体の単純な配列です。構造体は以下のようになります。

struct PyMethodDef {
    char *ml_name;
    PyCFunction ml_meth;
    int ml_flags;
    char *ml_doc;
};

この構造体のメンバの説明:

このテーブルは、適切なメンバにNULLと0の値を持つセンティネルで終了する必要があります。

例

上記で定義した関数のメソッドマッピングテーブル:

static PyMethodDef module_methods[] = {
    { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
    { NULL, NULL, 0, NULL }
};

初期化関数

拡張モジュールの最後の部分は初期化関数です。この関数は、モジュールが読み込まれたときにPythonインタープリターによって呼び出されます。関数名はinitModuleである必要があり、Moduleはモジュールの名前です。

初期化関数は、ビルドするライブラリからエクスポートされる必要があります。Pythonヘッダーは、特定の環境でコンパイルするために適切な呪文を含むPyMODINIT_FUNCを定義しています。これを関数定義時に使用するだけです。

C初期化関数は一般的に以下の全体構造を持ちます。

PyMODINIT_FUNC initModule() {
    Py_InitModule3(func, module_methods, "docstring...");
}

Py_InitModule3関数の説明:

すべてを組み合わせると以下のようになります。

#include 
static PyObject *module_func(PyObject *self, PyObject *args) {
    /* ここで処理を実行 */
    Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
    { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
    { NULL, NULL, 0, NULL }
};
PyMODINIT_FUNC initModule() {
    Py_InitModule3(func, module_methods, "docstring...");
}

完全な例

上記すべての概念を使用したシンプルな例:

#include 
static PyObject* helloworld(PyObject* self) {
    return Py_BuildValue("s", "Hello, Python extensions!!");
}
static char helloworld_docs[] = "helloworld( ): Any message you want to put here!!\n";
static PyMethodDef helloworld_funcs[] = {
    {"helloworld", (PyCFunction)helloworld, METH_NOARGS, helloworld_docs},
    {NULL}
};
void inithelloworld(void) {
    Py_InitModule3("helloworld", helloworld_funcs, "Extension module example!");
}

ここではPy_BuildValue関数を使用してPython値を構築しています。上記コードをhello.cファイルに保存します。このモジュールをコンパイルしてインストールし、Pythonスクリプトから呼び出せるようにする方法を見ていきます。

拡張のビルドとインストール

distutilsパッケージを使用すると、純粋なPythonと拡張モジュールの両方を標準的な方法で非常に簡単に配布できます。モジュールはソース形式で配布され、通常setup.pyと呼ばれるセットアップスクリプトを介してビルドおよびインストールされます。

上記モジュールの場合、以下のsetup.pyスクリプトを準備する必要があります。

from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',
      ext_modules=[Extension('helloworld', ['hello.c'])])

以下のコマンドを使用すると、適切なコンパイラとリンカのコマンドおよびフラグで必要なすべてのコンパイルとリンクの手順が実行され、結果の動的ライブラリが適切なディレクトリにコピーされます。

$ python setup.py install

Unixベースのシステムでは、site-packagesディレクトリに書き込む権限を得るためにrootとしてこのコマンドを実行する必要があるでしょう。これは通常Windowsでは問題になりません。

拡張のインポート

拡張をインストールしたら、Pythonスクリプトで以下のようにインポートして呼び出せます。

#!/usr/bin/python
import helloworld
print(helloworld.helloworld())

実行結果:

Hello, Python extensions!!

関数パラメータの受け渡し

引数を受け取る関数を定義したい場合がほとんどでしょう。その場合、C関数の他のシグネチャを使用できます。例えば、いくつかのパラメータを受け取る関数は以下のように定義されます。

static PyObject *module_func(PyObject *self, PyObject *args) {
    /* argsをパースして面白いことをここで実行 */
    Py_RETURN_NONE;
}

新しい関数のエントリを含むメソッドテーブル:

static PyMethodDef module_methods[] = {
    { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
    { "func", module_func, METH_VARARGS, NULL },
    { NULL, NULL, 0, NULL }
};

API関数PyArg_ParseTupleを使用して、C関数に渡された1つのPyObjectポインターから引数を抽出できます。

PyArg_ParseTupleの最初の引数はargs引数です。これはパースするオブジェクトです。2番目の引数は、引数がどのように現れることを期待するかを記述するフォーマット文字列です。各引数は、フォーマット文字列内の1つ以上の文字で表されます。

static PyObject *module_func(PyObject *self, PyObject *args) {
    int i;
    double d;
    char *s;
    if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
        return NULL;
    }
    /* ここで面白いことを実行 */
    Py_RETURN_NONE;
}

モジュールの新しいバージョンをコンパイルしてインポートすると、任意の数、任意の型の引数で新しい関数を呼び出せるようになります。

module.func(1, s="three", d=2.0)
module.func(i=1, d=2.0, s="three")
module.func(s="three", d=2.0, i=1)

さらに多くのバリエーションを考え出せるでしょう。

PyArg_ParseTuple関数

PyArg_ParseTuple関数の標準シグネチャ:

int PyArg_ParseTuple(PyObject* tuple, char* format, ...)

この関数はエラー時に0を返し、成功時は0以外の値を返します。tupleはC関数の2番目の引数であったPyObject*です。formatは、必須およびオプションの引数を記述するC文字列です。

PyArg_ParseTuple関数のフォーマットコード一覧:

コードC型意味
cchar長さ1のPython文字列がCのcharになる
ddoublePythonのfloatがCのdoubleになる
ffloatPythonのfloatがCのfloatになる
iintPythonのintがCのintになる
llongPythonのintがCのlongになる
Llong longPythonのintがCのlong longになる
OPyObject*Python引数への非NULL借用参照を取得
schar*埋め込みNULLなしのPython文字列をCのchar*に
s#char*+int任意のPython文字列をCのアドレスと長さに
t#char*+int読み取り専用シングルセグメントバッファをCのアドレスと長さに
uPy_UNICODE*埋め込みNULLなしのPython UnicodeをCに
u#Py_UNICODE*+int任意のPython UnicodeをCのアドレスと長さに
w#char*+int読み書き可能シングルセグメントバッファをCのアドレスと長さに
zchar*sと同様だが、Noneも受け入れる(Cのchar*をNULLに設定)
z#char*+ints#と同様だが、Noneも受け入れる(Cのchar*をNULLに設定)
(...)as per ...Pythonシーケンスを項目ごとに1引数として扱う
| 以降の引数はオプション
: フォーマット終了、エラーメッセージ用の関数名が続く
; フォーマット終了、エラーメッセージ全文が続く

値の返却

Py_BuildValueは、PyArg_ParseTupleと同様にフォーマット文字列を受け取ります。構築する値のアドレスを渡す代わりに、実際の値を渡します。add関数の実装例:

static PyObject *foo_add(PyObject *self, PyObject *args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
        return NULL;
    }
    return Py_BuildValue("i", a + b);
}

Pythonで実装すると以下のようになります。

def add(a, b):
    return (a + b)

関数から2つの値を返すこともできます。これはPythonではリストで取得されます。

static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
        return NULL;
    }
    return Py_BuildValue("ii", a + b, a - b);
}

Pythonで実装すると以下のようになります。

def add_subtract(a, b):
    return (a + b, a - b)

Py_BuildValue関数

Py_BuildValue関数の標準シグネチャ:

PyObject* Py_BuildValue(char* format, ...)

formatは構築するPythonオブジェクトを記述するC文字列です。Py_BuildValueの以降の引数は、結果の構築元となるCの値です。PyObject*の結果は新しい参照です。

以下の表に、ゼロ個以上結合してフォーマット文字列を形成する一般的に使用されるコード文字列を示します。

コードC型意味
ccharCのcharが長さ1のPython文字列になる
ddoubleCのdoubleがPythonのfloatになる
ffloatCのfloatがPythonのfloatになる
iintCのintがPythonのintになる
llongCのlongがPythonのintになる
NPyObject*Pythonオブジェクトを渡し、参照を盗む
OPyObject*Pythonオブジェクトを渡し、通常通りINCREF
O&convert+void*任意の変換
schar*CのNULL終端char*をPython文字列に、またはNULLをNoneに
s#char*+intCのchar*と長さをPython文字列に、またはNULLをNoneに
uPy_UNICODE*CのワイドNULL終端文字列をPython Unicodeに、またはNULLをNoneに
u#Py_UNICODE*+intCのワイド文字列と長さをPython Unicodeに、またはNULLをNoneに
w#char*+int読み書き可能シングルセグメントバッファをCのアドレスと長さに
zchar*sと同様、Noneも受け入れる(Cのchar*をNULLに設定)
z#char*+ints#と同様、Noneも受け入れる(Cのchar*をNULLに設定)
(...)as per ...Cの値からPythonタプルを構築
[...]as per ...Cの値からPythonリストを構築
{...}as per ...Cの値からPython辞書を構築、キーと値を交互に

コード{...}は、偶数個のC値から辞書を構築し、交互にキーと値となります。例えば、Py_BuildValue("{issi}",23,"zig","zag",42)は、Pythonの{23:'zig','zag':42}のような辞書を返します。

Python

  1. Pythonオブジェクト指向プログラミング完全ガイド:クラス、継承、カプセル化、ポリモーフィズム
  2. Python print()関数の実践ガイド:基本から応用まで
  3. Python strip()メソッド完全ガイド:実践例付き
  4. Python str.count()マスター:文字と部分文字列のカウント実例集
  5. Python format()文字列を明確な例でマスター
  6. Python String.find()メソッド完全ガイド:構文、例、代替手法
  7. Pythonラムダ関数をマスターする:実例とベストプラクティス
  8. Python round()関数を実例で徹底解説
  9. Python map()関数をマスター:構文、例、ベストプラクティス
  10. Python timeit():実行時間測定の実例付きガイド
  11. Python collections.Counter:効率的なカウント、更新、演算操作