Python 3-Cを使用した拡張プログラミング

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

Python拡張モジュールは、通常のCライブラリにすぎません。Unixマシンでは、これらのライブラリは通常、.so(共有オブジェクトの場合)。Windowsマシンでは、通常、.dll (動的にリンクされたライブラリの場合)。

拡張機能を作成するための前提条件

拡張機能の作成を開始するには、Pythonヘッダーファイルが必要になります。

  • Unixマシンでは、これには通常、python2.5-devなどの開発者固有のパッケージをインストールする必要があります。

  • Windowsユーザーは、バイナリPythonインストーラーを使用するときに、これらのヘッダーをパッケージの一部として取得します。

さらに、Cプログラミングを使用してPython拡張機能を作成するには、CまたはC ++に関する十分な知識があることを前提としています。

最初にPython拡張機能を見てください

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

  • ヘッダーファイルPython.h

  • モジュールからのインターフェースとして公開したいC関数。

  • Python開発者が関数を拡張モジュール内のC関数と見なすときに、関数の名前をマッピングするテーブル。

  • 初期化関数。

ヘッダーファイルPython.h

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

必要になる可能性のある他のヘッダーの前に、必ず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オブジェクトを返します。Pythonには、Cのようにvoid関数のようなものはありません。関数に値を返させたくない場合は、Pythonと同等のCを返します。None値。Pythonヘッダーは、これを行うマクロPy_RETURN_NONEを定義します。

C関数の名前は、拡張モジュールの外部では表示されないため、好きな名前にすることができます。それらは静的関数として定義されています。

C関数は通常、ここに示すように、Pythonモジュール名と関数名を組み合わせて名前が付けられます。

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   Py_RETURN_NONE;
}

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

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

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

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

これがこの構造のメンバーの説明です-

  • ml_name −これは、PythonインタープリターがPythonプログラムで使用されるときに表示される関数の名前です。

  • ml_meth −これは、前のセクションで説明したシグネチャのいずれかを持つ関数のアドレスです。

  • ml_flags −これは、ml_methが3つのシグニチャのどれを使用しているかをインタプリタに通知します。

    • このフラグの値は通常METH_VARARGSです。

    • 関数へのキーワード引数を許可する場合は、このフラグをMETH_KEYWORDSとビット単位でOR演算できます。

    • これは、引数を受け入れたくないことを示すMETH_NOARGSの値を持つこともできます。

  • ml_doc −これは関数のdocstringであり、書きたくない場合はNULLになる可能性があります。

このテーブルは、適切なメンバーの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 関数-

  • func −これはエクスポートされる関数です。

  • module_methods −これは上記で定義されたマッピングテーブル名です。

  • docstring −これはあなたがあなたの拡張機能に与えたいコメントです。

これらすべてをまとめると、次のようになります。

#include <Python.h>

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   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 <Python.h>

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モジュールを配布することは非常に簡単で、純粋なPythonと拡張モジュールの両方になります。モジュールはソース形式で配布され、通常setup.pyasと呼ばれるセットアップスクリプトを介してビルドおよびインストールされます。

上記のモジュールでは、次の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/python3
import helloworld

print helloworld.helloworld()

出力

これにより、次の結果が生成されます-

Hello, Python extensions!!

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

引数を受け入れる関数を定義する可能性が高いため、C関数には他のシグネチャの1つを使用できます。たとえば、いくつかのパラメータを受け入れる次の関数は、次のように定義されます。

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Parse args and do something interesting here. */
   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;
   }
   
   /* Do something interesting here. */
   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に等しくない値を返します。タプルは、C関数の2番目の引数であるPyObject *です。ここでのフォーマットは、必須およびオプションの引数を説明するC文字列です。

これがのフォーマットコードのリストです PyArg_ParseTuple 関数-

コード Cタイプ 意味
c char 長さ1のPython文字列はC文字になります。
d ダブル PythonフロートはCダブルになります。
f 浮く PythonフロートはCフロートになります。
int PythonintはCintになります。
l 長いです PythonintはClongになります。
L 長い長い PythonintはClonglongになります
O PyObject * Python引数へのNULL以外の借用参照を取得します。
s char * C char *にnullが埋め込まれていないPython文字列。
s# char * + int Cアドレスと長さへのPython文字列。
t# char * + int Cアドレスと長さへの読み取り専用の単一セグメントバッファ。
u Py_UNICODE * Cにnullが埋め込まれていないPythonUnicode。
u# Py_UNICODE * + int Python UnicodeCのアドレスと長さ。
w# char * + int シングルセグメントバッファをCアドレスと長さに読み取り/書き込みします。
z char * sと同様に、Noneも受け入れます(C char *をNULLに設定します)。
z# char * + int s#と同様に、Noneも受け入れます(C char *をNULLに設定します)。
(...) のように..。 Pythonシーケンスは、アイテムごとに1つの引数として扱われます。
| 次の引数はオプションです。
endをフォーマットし、その後にエラーメッセージの関数名を続けます。
; 終了をフォーマットし、その後にエラーメッセージテキスト全体を続けます。

戻り値

Py_BuildValueは、PyArg_ParseTupleと同じようにフォーマット文字列を取ります。構築している値のアドレスを渡す代わりに、実際の値を渡します。add関数を実装する方法を示す例を次に示します-

static PyObject *foo_add(PyObject *self, PyObject *args) {
   int a;
   int 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;
   int 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,...)

ここでのフォーマットは、ビルドするPythonオブジェクトを説明するC文字列です。Py_BuildValueの次の引数は、結果の作成元となるC値です。PyObject *の結果は、新たなリファレンスです。

次の表に、一般的に使用されるコード文字列を示します。そのうちの0個以上が文字列形式に結合されています。

コード Cタイプ 意味
c char AC文字は長さ1のPython文字列になります。
d ダブル ACdoubleはPythonフロートになります。
f 浮く ACフロートはPythonフロートになります。
int ACintはPythonintになります。
l 長いです AClongはPythonintになります。
N PyObject * Pythonオブジェクトを渡し、参照を盗みます。
O PyObject * Pythonオブジェクトを渡し、通常どおりINCREFします。
O& convert + void * 任意の変換
s char * C0で終了するchar *からPython文字列、またはNULLからNone。
s# char * + int C char *と長さをPython文字列に、またはNULLをNoneに。
u Py_UNICODE * C全体、nullで終了する文字列からPython Unicode、またはNULLからNone。
u# Py_UNICODE * + int C全体の文字列と長さはPythonUnicodeに、NULLはNoneに。
w# char * + int シングルセグメントバッファをCアドレスと長さに読み取り/書き込みします。
z char * sと同様に、Noneも受け入れます(C char *をNULLに設定します)。
z# char * + int s#と同様に、Noneも受け入れます(C char *をNULLに設定します)。
(...) のように..。 C値からPythonタプルを構築します。
[...] のように..。 C値からPythonリストを作成します。
{...} のように..。 C値、交互のキーと値からPython辞書を構築します。

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