1. CまたはC ++によるPythonの拡張—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.8/extending/extending
移動先:案内検索

1.1。 CまたはC ++でPythonを拡張する

Cでプログラミングする方法を知っていれば、Pythonに新しい組み込みモジュールを追加するのは非常に簡単です。 このような拡張モジュールは、Pythonでは直接実行できない2つのことを実行できます。新しい組み込みオブジェクトタイプを実装できることと、Cライブラリ関数とシステムコールを呼び出すことができることです。

拡張機能をサポートするために、Python API(Application Programmers Interface)は、Pythonランタイムシステムのほとんどの側面へのアクセスを提供する一連の関数、マクロ、および変数を定義します。 Python APIは、ヘッダー"Python.h"を含めることでCソースファイルに組み込まれます。

拡張モジュールのコンパイルは、その使用目的とシステム設定によって異なります。 詳細については、後の章で説明します。

ノート

C拡張インターフェースはCPythonに固有であり、拡張モジュールは他のPython実装では機能しません。 多くの場合、C拡張機能の記述を回避し、他の実装への移植性を維持することが可能です。 たとえば、ユースケースがCライブラリ関数またはシステムコールの呼び出しである場合、カスタムCコードを作成するのではなく、 ctypes モジュールまたは cffi ライブラリの使用を検討する必要があります。 これらのモジュールを使用すると、CコードとインターフェイスするPythonコードを記述でき、C拡張モジュールを記述してコンパイルするよりもPythonの実装間で移植性が高くなります。


1.1。 簡単な例

spam(Monty Pythonファンのお気に入りの食べ物…)という拡張モジュールを作成して、Cライブラリ関数system() 1 へのPythonインターフェイスを作成するとします。 。 この関数は、nullで終了する文字列を引数として受け取り、整数を返します。 この関数をPythonから次のように呼び出すことができるようにします。

>>> import spam
>>> status = spam.system("ls -l")

ファイルspammodule.cを作成することから始めます。 (これまで、モジュールがspamと呼ばれる場合、その実装を含むCファイルはspammodule.cと呼ばれます。モジュール名がspammifyのように非常に長い場合、モジュール名は次のようになります。ただspammify.cになります。)

ファイルの最初の2行は次のようになります。

#define PY_SSIZE_T_CLEAN
#include <Python.h>

Python APIをプルします(必要に応じて、モジュールの目的を説明するコメントと著作権表示を追加できます)。

ノート

Pythonは、一部のシステムの標準ヘッダーに影響を与えるプリプロセッサ定義を定義する場合があるため、標準ヘッダーを含める前に、 Python.hを含める必要があります。

Python.hを含める前に、常にPY_SSIZE_T_CLEANを定義することをお勧めします。 このマクロの説明については、拡張関数のパラメーターの抽出を参照してください。


Python.hで定義されているすべてのユーザーに表示されるシンボルには、標準のヘッダーファイルで定義されているものを除き、PyまたはPYのプレフィックスが付いています。 便宜上、またPythonインタープリターによって広く使用されているため、"Python.h"には、<stdio.h><string.h><errno.h>、および[ X165X] 。 後者のヘッダーファイルがシステムに存在しない場合は、関数malloc()free()、およびrealloc()を直接宣言します。

次にモジュールファイルに追加するのは、Python式spam.system(string)が評価されるときに呼び出されるC関数です(どのように呼び出されるかはすぐにわかります)。

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return PyLong_FromLong(sts);
}

Pythonの引数リスト(たとえば、単一の式"ls -l")からC関数に渡される引数への簡単な変換があります。 C関数には常に2つの引数があり、通常は selfargs という名前が付けられています。

self 引数は、モジュールレベルの関数のモジュールオブジェクトを指します。 メソッドの場合、オブジェクトインスタンスを指します。

args 引数は、引数を含むPythonタプルオブジェクトへのポインターになります。 タプルの各項目は、呼び出しの引数リストの引数に対応します。 引数はPythonオブジェクトです。C関数で引数を使用するには、引数をC値に変換する必要があります。 PythonAPIの関数 PyArg_ParseTuple()は、引数の型をチェックし、それらをC値に変換します。 テンプレート文字列を使用して、必要な引数のタイプと、変換された値を格納するC変数のタイプを決定します。 これについては後で詳しく説明します。

PyArg_ParseTuple()は、すべての引数が正しい型であり、そのコンポーネントがアドレスが渡される変数に格納されている場合、true(ゼロ以外)を返します。 無効な引数リストが渡された場合はfalse(ゼロ)を返します。 後者の場合、適切な例外も発生するため、呼び出し元の関数はNULLをすぐに返すことができます(例で見たように)。


1.2。 Intermezzo:エラーと例外

Pythonインタープリター全体の重要な規則は、次のとおりです。関数が失敗すると、例外条件を設定し、エラー値(通常はNULLポインター)を返す必要があります。 例外は、インタープリター内の静的グローバル変数に格納されます。 この変数がNULLの場合、例外は発生していません。 2番目のグローバル変数は、例外の「関連付けられた値」を格納します( raise の2番目の引数)。 3番目の変数には、エラーがPythonコードで発生した場合のスタックトレースバックが含まれています。 これらの3つの変数は、Pythonでの sys.exc_info()の結果に相当するCです(Pythonライブラリリファレンスのモジュール sys のセクションを参照)。 エラーがどのように渡されるかを理解するには、それらについて知ることが重要です。

Python APIは、さまざまなタイプの例外を設定するためのいくつかの関数を定義しています。

最も一般的なものは PyErr_SetString()です。 その引数は、例外オブジェクトとC文字列です。 例外オブジェクトは通常、PyExc_ZeroDivisionErrorのような事前定義されたオブジェクトです。 C文字列はエラーの原因を示し、Python文字列オブジェクトに変換され、例外の「関連付けられた値」として格納されます。

もう1つの便利な関数は、 PyErr_SetFromErrno()です。これは、例外引数のみを受け取り、グローバル変数errnoを調べて関連する値を作成します。 最も一般的な関数は PyErr_SetObject()で、例外とそれに関連する値の2つのオブジェクト引数を取ります。 これらの関数のいずれかに渡されるオブジェクトを Py_INCREF()する必要はありません。

PyErr_Occurred()を使用して、例外が設定されているかどうかを非破壊的にテストできます。 これにより、現在の例外オブジェクトが返されます。例外が発生していない場合は、NULLが返されます。 通常、 PyErr_Occurred()を呼び出して、関数呼び出しでエラーが発生したかどうかを確認する必要はありません。戻り値からわかるはずだからです。

別の関数 g を呼び出す関数 f が後者の失敗を検出すると、 f 自体がエラー値(通常はNULLまたは[ X166X] )。 PyErr _ * 関数の1つを呼び出さない必要があります—1つは g によってすでに呼び出されています。 f の呼び出し元は、その呼び出し元にもエラー表示を返し、 PyErr _ * を呼び出さずにを返すことになっています。 —エラーの最も詳細な原因は、最初にエラーを検出した関数によってすでに報告されています。 エラーがPythonインタープリターのメインループに到達すると、これは現在実行中のPythonコードを中止し、Pythonプログラマーによって指定された例外ハンドラーを見つけようとします。

(モジュールが別の PyErr _ * 関数を呼び出すことによって、実際により詳細なエラーメッセージを表示できる場合があります。そのような場合は、そうしても問題ありません。 ただし、原則として、これは必須ではなく、エラーの原因に関する情報が失われる可能性があります。ほとんどの操作は、さまざまな理由で失敗する可能性があります。)

失敗した関数呼び出しによって設定された例外を無視するには、 PyErr_Clear()を呼び出して例外条件を明示的にクリアする必要があります。 Cコードが PyErr_Clear()を呼び出す必要があるのは、エラーをインタープリターに渡したくないが、それ自体で完全に処理したい場合だけです(おそらく、何か他のことを試すか、何もしなかったふりをすることによって)間違い)。

失敗したmalloc()呼び出しはすべて例外に変換する必要があります— malloc()(またはrealloc())の直接呼び出し元は、 PyErr_NoMemory()を呼び出して失敗を返す必要がありますインジケーター自体。 すべてのオブジェクト作成関数(たとえば、 PyLong_FromLong())はすでにこれを行っているため、このメモはmalloc()を直接呼び出す人にのみ関係します。

また、 PyArg_ParseTuple()とその仲間を除いて、Unixシステムコールのように、整数ステータスを返す関数は通常、成功の場合は正の値またはゼロを返し、失敗の場合は-1を返します。 。

最後に、エラーインジケータを返すときは、ガベージをクリーンアップするように注意してください( Py_XDECREF()または Py_DECREF()で作成済みのオブジェクトを呼び出します)。

どの例外を発生させるかの選択は完全にあなた次第です。 PyExc_ZeroDivisionErrorなど、直接使用できるすべての組み込みPython例外に対応する事前宣言されたCオブジェクトがあります。 もちろん、例外は慎重に選択する必要があります。PyExc_TypeErrorを使用して、ファイルを開くことができなかったことを意味しないでください(おそらく、PyExc_IOErrorである必要があります)。 引数リストに問題がある場合、 PyArg_ParseTuple()関数は通常PyExc_TypeErrorを発生させます。 値が特定の範囲内にあるか、他の条件を満たす必要がある引数がある場合は、PyExc_ValueErrorが適切です。

モジュールに固有の新しい例外を定義することもできます。 このため、通常、ファイルの先頭で静的オブジェクト変数を宣言します。

static PyObject *SpamError;

モジュールの初期化関数(PyInit_spam())で、例外オブジェクトを使用して初期化します。

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_XINCREF(SpamError);
    if (PyModule_AddObject(m, "error", SpamError) < 0) {
        Py_XDECREF(SpamError);
        Py_CLEAR(SpamError);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

例外オブジェクトのPython名はspam.errorであることに注意してください。 PyErr_NewException()関数は、 Builtで説明されているように、基本クラスが Exception であるクラスを作成できます(NULLの代わりに別のクラスが渡されない限り) -例外。

SpamError変数は、新しく作成された例外クラスへの参照を保持することにも注意してください。 これは意図的なものです! 例外は外部コードによってモジュールから削除される可能性があるため、クラスへの所有された参照は、それが破棄されないようにするために必要であり、SpamErrorがダングリングポインターになります。 ダングリングポインタになった場合、例外を発生させるCコードは、コアダンプまたはその他の意図しない副作用を引き起こす可能性があります。

このサンプルの後半で、関数の戻り型としてPyMODINIT_FUNCを使用する方法について説明します。

spam.error例外は、以下に示すように、 PyErr_SetString()の呼び出しを使用して拡張モジュールで発生させることができます。

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}

1.3。 例に戻る

関数の例に戻ると、次のステートメントを理解できるはずです。

if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;

PyArg_ParseTuple()によって設定された例外に依存して、引数リストでエラーが検出された場合、NULL(オブジェクトポインタを返す関数のエラーインジケータ)を返します。 それ以外の場合、引数の文字列値はローカル変数commandにコピーされています。 これはポインタの割り当てであり、それが指す文字列を変更することは想定されていません(したがって、標準Cでは、変数commandconst char *commandとして適切に宣言する必要があります)。

次のステートメントは、Unix関数system()の呼び出しであり、 PyArg_ParseTuple()から取得した文字列を渡します。

sts = system(command);

spam.system()関数は、stsの値をPythonオブジェクトとして返す必要があります。 これは、関数 PyLong_FromLong()を使用して実行されます。

return PyLong_FromLong(sts);

この場合、整数オブジェクトを返します。 (はい、整数でさえPythonのヒープ上のオブジェクトです!)

有用な引数を返さないC関数( void を返す関数)がある場合、対応するPython関数はNoneを返す必要があります。 これを行うには、このイディオムが必要です( Py_RETURN_NONE マクロによって実装されます)。

Py_INCREF(Py_None);
return Py_None;

Py_None は、特別なPythonオブジェクトNoneのC名です。 これは、NULLポインターではなく、本物のPythonオブジェクトです。これは、これまで見てきたように、ほとんどのコンテキストで「エラー」を意味します。


1.4。 モジュールのメソッドテーブルと初期化関数

Pythonプログラムからspam_system()がどのように呼び出されるかを示すことを約束しました。 まず、その名前とアドレスを「メソッドテーブル」にリストする必要があります。

static PyMethodDef SpamMethods[] = {
    ...
    {"system",  spam_system, METH_VARARGS,
     "Execute a shell command."},
    ...
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

3番目のエントリ(METH_VARARGS)に注意してください。 これは、C関数に使用される呼び出し規約をインタプリタに通知するフラグです。 通常は常にMETH_VARARGSまたはMETH_VARARGS | METH_KEYWORDSである必要があります。 0の値は、 PyArg_ParseTuple()の廃止されたバリアントが使用されていることを意味します。

METH_VARARGSのみを使用する場合、関数は、Pythonレベルのパラメーターが PyArg_ParseTuple()を介して解析できるタプルとして渡されることを期待する必要があります。 この機能の詳細については、以下を参照してください。

キーワード引数を関数に渡す必要がある場合は、METH_KEYWORDSビットを3番目のフィールドに設定できます。 この場合、C関数は、キーワードの辞書となる3番目のPyObject *パラメーターを受け入れる必要があります。 PyArg_ParseTupleAndKeywords()を使用して、このような関数の引数を解析します。

メソッドテーブルは、モジュール定義構造で参照する必要があります。

static struct PyModuleDef spammodule = {
    PyModuleDef_HEAD_INIT,
    "spam",   /* name of module */
    spam_doc, /* module documentation, may be NULL */
    -1,       /* size of per-interpreter state of the module,
                 or -1 if the module keeps state in global variables. */
    SpamMethods
};

この構造は、モジュールの初期化関数でインタプリタに渡される必要があります。 初期化関数の名前はPyInit_name()である必要があります。ここで、 name はモジュールの名前であり、モジュールファイルで定義されている唯一の非staticアイテムである必要があります。

PyMODINIT_FUNC
PyInit_spam(void)
{
    return PyModule_Create(&spammodule);
}

PyMODINIT_FUNCは、関数をPyObject *リターン型として宣言し、プラットフォームに必要な特別なリンケージ宣言を宣言し、C ++の場合は関数をextern "C"として宣言することに注意してください。

Pythonプログラムがモジュールspamを初めてインポートすると、PyInit_spam()が呼び出されます。 (Pythonの埋め込みに関するコメントについては、以下を参照してください。)モジュールオブジェクトを返す PyModule_Create()を呼び出し、テーブル([X219Xの配列]に基づいて新しく作成されたモジュールに組み込み関数オブジェクトを挿入します。 ] PyMethodDef 構造体)がモジュール定義にあります。 PyModule_Create()は、作成するモジュールオブジェクトへのポインターを返します。 特定のエラーで致命的なエラーで異常終了するか、モジュールを十分に初期化できなかった場合はNULLを返すことがあります。 init関数は、モジュールオブジェクトを呼び出し元に返して、sys.modulesに挿入する必要があります。

Pythonを埋め込む場合、PyImport_Inittabテーブルにエントリがない限り、PyInit_spam()関数は自動的に呼び出されません。 モジュールを初期化テーブルに追加するには、 PyImport_AppendInittab()を使用し、オプションでモジュールをインポートします。

int
main(int argc, char *argv[])
{
    wchar_t *program = Py_DecodeLocale(argv[0], NULL);
    if (program == NULL) {
        fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
        exit(1);
    }

    /* Add a built-in module, before Py_Initialize */
    if (PyImport_AppendInittab("spam", PyInit_spam) == -1) {
        fprintf(stderr, "Error: could not extend in-built modules table\n");
        exit(1);
    }

    /* Pass argv[0] to the Python interpreter */
    Py_SetProgramName(program);

    /* Initialize the Python interpreter.  Required.
       If this step fails, it will be a fatal error. */
    Py_Initialize();

    /* Optionally import the module; alternatively,
       import can be deferred until the embedded script
       imports it. */
    PyObject *pmodule = PyImport_ImportModule("spam");
    if (!pmodule) {
        PyErr_Print();
        fprintf(stderr, "Error: could not import module 'spam'\n");
    }

    ...

    PyMem_RawFree(program);
    return 0;
}

ノート

sys.modulesからエントリを削除したり、コンパイルされたモジュールをプロセス内の複数のインタプリタにインポートしたり(または、exec()を介さずにfork()をフォローしたり)、一部の拡張モジュールで問題が発生する可能性があります。 拡張モジュールの作成者は、内部データ構造を初期化するときに注意を払う必要があります。


より実質的なサンプルモジュールは、Modules/xxmodule.cとしてPythonソースディストリビューションに含まれています。 このファイルは、テンプレートとして使用することも、単に例として読むこともできます。

ノート

spamの例とは異なり、xxmoduleマルチフェーズ初期化(Python 3.5の新機能)を使用します。PyModuleDef構造はPyInit_spamから返されます。モジュールの一部は輸入機械に任されています。 マルチフェーズ初期化の詳細については、 PEP 489 を参照してください。


1.5。 コンパイルとリンケージ

新しい拡張機能を使用する前に、コンパイルしてPythonシステムにリンクするという2つのことを行う必要があります。 動的ロードを使用する場合、詳細はシステムが使用する動的ロードのスタイルによって異なる場合があります。 詳細については、拡張モジュールの構築に関する章( CおよびC ++拡張の構築)およびWindowsでの構築のみに関連する追加情報( WindowsでのCおよびC ++拡張の構築)を参照してください。これについて。

動的ロードを使用できない場合、またはモジュールをPythonインタープリターの永続的な部分にしたい場合は、構成セットアップを変更してインタープリターを再構築する必要があります。 幸い、これはUnixでは非常に簡単です。ファイル(spammodule.cなど)を解凍したソースディストリビューションのModules/ディレクトリに配置し、ファイルModules/Setup.localに行を追加するだけです。ファイルの説明:

spam spammodule.o

トップレベルディレクトリで make を実行して、インタプリタを再構築します。 Modules/サブディレクトリで make を実行することもできますが、最初に「 make Makefile」を実行してMakefileを再構築する必要があります。 (これは、Setupファイルを変更するたびに必要です。)

モジュールにリンクする追加のライブラリが必要な場合は、これらを構成ファイルの行にリストすることもできます。たとえば、次のようになります。

spam spammodule.o -lX11

1.6。 CからPython関数を呼び出す

これまで、PythonからC関数を呼び出し可能にすることに集中してきました。 逆も便利です。CからPython関数を呼び出すことです。 これは、いわゆる「コールバック」関数をサポートするライブラリの場合に特に当てはまります。 Cインターフェースがコールバックを利用する場合、同等のPythonは多くの場合Pythonプログラマーにコールバックメカニズムを提供する必要があります。 実装では、CコールバックからPythonコールバック関数を呼び出す必要があります。 他の用途も考えられます。

幸い、Pythonインタープリターは簡単に再帰的に呼び出され、Python関数を呼び出すための標準インターフェースがあります。 (特定の文字列を入力としてPythonパーサーを呼び出す方法については詳しく説明しません。興味がある場合は、 [の -c コマンドラインオプションの実装をご覧ください。 X186X] Pythonソースコードから。)

Python関数の呼び出しは簡単です。 まず、PythonプログラムはどういうわけかPython関数オブジェクトを渡す必要があります。 これを行うには、関数(またはその他のインターフェイス)を提供する必要があります。 この関数が呼び出されたら、Python関数オブジェクトへのポインターをグローバル変数に保存します( Py_INCREF()に注意してください!)。 たとえば、次の関数はモジュール定義の一部である可能性があります。

static PyObject *my_callback = NULL;

static PyObject *
my_set_callback(PyObject *dummy, PyObject *args)
{
    PyObject *result = NULL;
    PyObject *temp;

    if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
        if (!PyCallable_Check(temp)) {
            PyErr_SetString(PyExc_TypeError, "parameter must be callable");
            return NULL;
        }
        Py_XINCREF(temp);         /* Add a reference to new callback */
        Py_XDECREF(my_callback);  /* Dispose of previous callback */
        my_callback = temp;       /* Remember new callback */
        /* Boilerplate to return "None" */
        Py_INCREF(Py_None);
        result = Py_None;
    }
    return result;
}

この関数は、 METH_VARARGS フラグを使用してインタープリターに登録する必要があります。 これについては、セクションモジュールのメソッドテーブルと初期化関数で説明されています。 PyArg_ParseTuple()関数とその引数は、セクション拡張関数のパラメーターの抽出に記載されています。

マクロ Py_XINCREF()および Py_XDECREF()は、オブジェクトの参照カウントをインクリメント/デクリメントし、NULLポインターが存在する場合でも安全です(ただし、[X178X ] temp は、このコンテキストではNULLにはなりません)。 詳細については、セクション参照カウントを参照してください。

後で関数を呼び出すときは、C関数 PyObject_CallObject()を呼び出します。 この関数には2つの引数があり、どちらも任意のPythonオブジェクトへのポインターです。Python関数と引数リストです。 引数リストは常にタプルオブジェクトである必要があり、その長さは引数の数です。 引数なしでPython関数を呼び出すには、NULLまたは空のタプルを渡します。 1つの引数で呼び出すには、シングルトンタプルを渡します。 Py_BuildValue()は、フォーマット文字列が括弧で囲まれた0個以上のフォーマットコードで構成されている場合にタプルを返します。 例えば:

int arg;
PyObject *arglist;
PyObject *result;
...
arg = 123;
...
/* Time to call the callback */
arglist = Py_BuildValue("(i)", arg);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);

PyObject_CallObject()はPythonオブジェクトポインターを返します。これはPython関数の戻り値です。 PyObject_CallObject()は、引数に関して「参照カウント中立」です。 この例では、引数リストとして機能する新しいタプルが作成されました。これは、 PyObject_CallObject()呼び出しの直後に Py_DECREF() -edされます。

PyObject_CallObject()の戻り値は「新規」です。これは、まったく新しいオブジェクトであるか、参照カウントが増加した既存のオブジェクトです。 したがって、グローバル変数に保存する場合を除いて、その値に関心がない場合でも、(特に!)結果を Py_DECREF()する必要があります。

ただし、これを行う前に、戻り値がNULLでないことを確認することが重要です。 そうである場合、Python関数は例外を発生させることによって終了しました。 PyObject_CallObject()を呼び出したCコードがPythonから呼び出された場合、インタープリターがスタックトレースを出力できるように、または呼び出し元のPythonコードが例外を処理できるように、Python呼び出し元にエラー表示を返す必要があります。 。 これが不可能または望ましくない場合は、 PyErr_Clear()を呼び出して例外をクリアする必要があります。 例えば:

if (result == NULL)
    return NULL; /* Pass error back */
...use result...
Py_DECREF(result);

Pythonコールバック関数への必要なインターフェイスによっては、 PyObject_CallObject()に引数リストを提供する必要がある場合もあります。 場合によっては、引数リストは、コールバック関数を指定したのと同じインターフェイスを介して、Pythonプログラムによって提供されます。 その後、関数オブジェクトと同じ方法で保存して使用することができます。 また、引数リストとして渡すために、新しいタプルを作成する必要がある場合もあります。 これを行う最も簡単な方法は、 Py_BuildValue()を呼び出すことです。 たとえば、統合イベントコードを渡したい場合は、次のコードを使用できます。

PyObject *arglist;
...
arglist = Py_BuildValue("(l)", eventcode);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
if (result == NULL)
    return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);

Py_DECREF(arglist)の配置は、呼び出しの直後、エラーチェックの前に注意してください。 また、厳密に言えば、このコードは完全ではないことに注意してください。 Py_BuildValue()のメモリが不足する可能性があるため、これを確認する必要があります。

PyObject_Call()を使用して、キーワード引数を使用して関数を呼び出すこともできます。これは、引数とキーワード引数をサポートします。 上記の例のように、 Py_BuildValue()を使用して辞書を作成します。

PyObject *dict;
...
dict = Py_BuildValue("{s:i}", "name", val);
result = PyObject_Call(my_callback, NULL, dict);
Py_DECREF(dict);
if (result == NULL)
    return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);

1.7。 拡張関数でのパラメータの抽出

PyArg_ParseTuple()関数は次のように宣言されています。

int PyArg_ParseTuple(PyObject *arg, const char *format, ...);

arg 引数は、PythonからC関数に渡される引数リストを含むタプルオブジェクトである必要があります。 format 引数はフォーマット文字列である必要があります。その構文は、Python / CAPIリファレンスマニュアルの引数の解析と値の構築で説明されています。 残りの引数は、タイプがフォーマット文字列によって決定される変数のアドレスである必要があります。

PyArg_ParseTuple()は、Python引数に必要な型があることを確認しますが、呼び出しに渡されたC変数のアドレスの有効性を確認できないことに注意してください。そこで間違えると、コードがクラッシュするか、少なくともメモリ内のランダムビットを上書きします。 ので注意してください!

呼び出し元に提供されるPythonオブジェクト参照は、借用参照であることに注意してください。 参照カウントをデクリメントしないでください!

いくつかの呼び出し例:

#define PY_SSIZE_T_CLEAN  /* Make "s#" use Py_ssize_t rather than int. */
#include <Python.h>
int ok;
int i, j;
long k, l;
const char *s;
Py_ssize_t size;

ok = PyArg_ParseTuple(args, ""); /* No arguments */
    /* Python call: f() */
ok = PyArg_ParseTuple(args, "s", &s); /* A string */
    /* Possible Python call: f('whoops!') */
ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */
    /* Possible Python call: f(1, 2, 'three') */
ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
    /* A pair of ints and a string, whose size is also returned */
    /* Possible Python call: f((1, 2), 'three') */
{
    const char *file;
    const char *mode = "r";
    int bufsize = 0;
    ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
    /* A string, and optionally another string and an integer */
    /* Possible Python calls:
       f('spam')
       f('spam', 'w')
       f('spam', 'wb', 100000) */
}
{
    int left, top, right, bottom, h, v;
    ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
             &left, &top, &right, &bottom, &h, &v);
    /* A rectangle and a point */
    /* Possible Python call:
       f(((0, 0), (400, 300)), (10, 10)) */
}
{
    Py_complex c;
    ok = PyArg_ParseTuple(args, "D:myfunction", &c);
    /* a complex, also providing a function name for errors */
    /* Possible Python call: myfunction(1+2j) */
}

1.8。 拡張関数のキーワードパラメータ

PyArg_ParseTupleAndKeywords()関数は次のように宣言されています。

int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,
                                const char *format, char *kwlist[], ...);

arg および format パラメーターは、 PyArg_ParseTuple()関数のパラメーターと同じです。 kwdict パラメーターは、Pythonランタイムから3番目のパラメーターとして受け取ったキーワードの辞書です。 kwlist パラメーターは、パラメーターを識別するNULLで終了する文字列のリストです。 名前は、左から右に format のタイプ情報と照合されます。 成功すると、 PyArg_ParseTupleAndKeywords()はtrueを返します。それ以外の場合は、falseを返し、適切な例外を発生させます。

ノート

キーワード引数を使用する場合、ネストされたタプルは解析できません。 kwlist に存在しない渡されたキーワードパラメータにより、 TypeError が発生します。


Geoff Philbrick( [email protected] )の例に基づいて、キーワードを使用するモジュールの例を次に示します。

#define PY_SSIZE_T_CLEAN  /* Make "s#" use Py_ssize_t rather than int. */
#include <Python.h>

static PyObject *
keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)
{
    int voltage;
    const char *state = "a stiff";
    const char *action = "voom";
    const char *type = "Norwegian Blue";

    static char *kwlist[] = {"voltage", "state", "action", "type", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist,
                                     &voltage, &state, &action, &type))
        return NULL;

    printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",
           action, voltage);
    printf("-- Lovely plumage, the %s -- It's %s!\n", type, state);

    Py_RETURN_NONE;
}

static PyMethodDef keywdarg_methods[] = {
    /* The cast of the function is necessary since PyCFunction values
     * only take two PyObject* parameters, and keywdarg_parrot() takes
     * three.
     */
    {"parrot", (PyCFunction)(void(*)(void))keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
     "Print a lovely skit to standard output."},
    {NULL, NULL, 0, NULL}   /* sentinel */
};

static struct PyModuleDef keywdargmodule = {
    PyModuleDef_HEAD_INIT,
    "keywdarg",
    NULL,
    -1,
    keywdarg_methods
};

PyMODINIT_FUNC
PyInit_keywdarg(void)
{
    return PyModule_Create(&keywdargmodule);
}

1.9。 任意の値の構築

この関数は、 PyArg_ParseTuple()に対応します。 次のように宣言されています。

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

PyArg_ParseTuple()で認識されるものと同様のフォーマット単位のセットを認識しますが、引数(出力ではなく関数に入力される)はポインターではなく、値のみである必要があります。 Pythonから呼び出されたC関数から戻るのに適した新しいPythonオブジェクトを返します。

PyArg_ParseTuple()との1つの違い:後者では最初の引数がタプルである必要がありますが(Python引数リストは常に内部でタプルとして表されるため)、 Py_BuildValue()は常にビルドされるとは限りません。タプル。 フォーマット文字列に2つ以上のフォーマット単位が含まれている場合にのみ、タプルを作成します。 フォーマット文字列が空の場合、Noneを返します。 フォーマットユニットが1つだけ含まれている場合は、そのフォーマットユニットによって記述されているオブジェクトを返します。 サイズ0または1のタプルを強制的に返すには、フォーマット文字列を括弧で囲みます。

例(左側の呼び出し、右側の結果のPython値):

Py_BuildValue("")                        None
Py_BuildValue("i", 123)                  123
Py_BuildValue("iii", 123, 456, 789)      (123, 456, 789)
Py_BuildValue("s", "hello")              'hello'
Py_BuildValue("y", "hello")              b'hello'
Py_BuildValue("ss", "hello", "world")    ('hello', 'world')
Py_BuildValue("s#", "hello", 4)          'hell'
Py_BuildValue("y#", "hello", 4)          b'hell'
Py_BuildValue("()")                      ()
Py_BuildValue("(i)", 123)                (123,)
Py_BuildValue("(ii)", 123, 456)          (123, 456)
Py_BuildValue("(i,i)", 123, 456)         (123, 456)
Py_BuildValue("[i,i]", 123, 456)         [123, 456]
Py_BuildValue("{s:i,s:i}",
              "abc", 123, "def", 456)    {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)",
              1, 2, 3, 4, 5, 6)          (((1, 2), (3, 4)), (5, 6))

1.10。 参照カウント

CやC ++などの言語では、プログラマーはヒープ上のメモリの動的な割り当てと割り当て解除を担当します。 Cでは、これは関数malloc()およびfree()を使用して行われます。 C ++では、演算子newdeleteは基本的に同じ意味で使用されるため、以下の説明はCの場合に限定します。

malloc()で割り当てられたメモリのすべてのブロックは、free()を1回呼び出すだけで、最終的に使用可能なメモリのプールに戻されます。 適切なタイミングでfree()を呼び出すことが重要です。 ブロックのアドレスを忘れてもfree()が呼び出されない場合、プログラムが終了するまで、ブロックが占有しているメモリを再利用することはできません。 これはメモリリークと呼ばれます。 一方、プログラムがブロックに対してfree()を呼び出した後、そのブロックを引き続き使用すると、別のmalloc()呼び出しによるブロックの再利用との競合が発生します。 これは、解放されたメモリを使用する ' と呼ばれます。 初期化されていないデータを参照するのと同じ悪い結果があります—コアダンプ、間違った結果、不思議なクラッシュ。

メモリリークの一般的な原因は、コード内の異常なパスです。 たとえば、関数はメモリのブロックを割り当て、計算を行ってから、ブロックを再度解放する場合があります。 関数の要件を変更すると、計算にテストが追加され、エラー状態が検出され、関数から途中で戻る可能性があります。 この時期尚早な出口をとるとき、特にそれが後でコードに追加されるとき、割り当てられたメモリブロックを解放することを忘れがちです。 このようなリークは、一度導入されると、長い間検出されないことがよくあります。エラーの終了は、すべての呼び出しのごく一部でのみ行われ、最近のほとんどのマシンには十分な仮想メモリがあるため、リークは長時間実行されるプロセスでのみ明らかになります。リーク機能を頻繁に使用します。 したがって、この種のエラーを最小限に抑えるコーディング規則または戦略を用意して、リークの発生を防ぐことが重要です。

Pythonはmalloc()free()を多用するため、メモリリークと解放されたメモリの使用を回避するための戦略が必要です。 選択された方法は、参照カウントと呼ばれます。 原理は単純です。すべてのオブジェクトにはカウンターが含まれています。カウンターは、オブジェクトへの参照がどこかに格納されるとインクリメントされ、オブジェクトへの参照が削除されるとデクリメントされます。 カウンターがゼロに達すると、オブジェクトへの最後の参照が削除され、オブジェクトが解放されます。

別の戦略は、自動ガベージコレクションと呼ばれます。 (参照カウントはガベージコレクション戦略とも呼ばれるため、2つを区別するために「自動」を使用します。)自動ガベージコレクションの大きな利点は、ユーザーがfree()を呼び出す必要がないことです。 ]明示的に。 (もう1つの主張されている利点は、速度またはメモリ使用量の改善です。ただし、これは難しい事実ではありません。)欠点は、Cの場合、真にポータブルな自動ガベージコレクターがない一方で、参照カウントを移植可能に実装できることです(関数が機能する限り)。 malloc()およびfree()が利用可能です—これはC標準で保証されています)。 たぶんいつか、十分にポータブルな自動ガベージコレクターがCで利用できるようになるでしょう。 それまでは、参照カウントを使用する必要があります。

Pythonは従来の参照カウントの実装を使用しますが、参照サイクルを検出するように機能するサイクル検出器も提供します。 これにより、アプリケーションは直接または間接の循環参照の作成について心配する必要がなくなります。 これらは、参照カウントのみを使用して実装されたガベージコレクションの弱点です。 参照サイクルは、それ自体への(場合によっては間接的な)参照を含むオブジェクトで構成されているため、サイクル内の各オブジェクトの参照カウントはゼロ以外です。 通常の参照カウントの実装では、サイクル自体への参照がそれ以上ない場合でも、参照サイクル内のオブジェクトに属するメモリ、またはサイクル内のオブジェクトから参照されるメモリを再利用することはできません。

サイクル検出器は、ガベージサイクルを検出し、それらを再利用できます。 gc モジュールは、検出器を実行する方法( collect()関数)、構成インターフェイス、および実行時に検出器を無効にする機能を公開します。 サイクル検出器はオプションのコンポーネントと見なされます。 デフォルトで含まれていますが、Unixプラットフォーム(Mac OS Xを含む)の configure スクリプトの--without-cycle-gcオプションを使用して、ビルド時に無効にすることができます。 この方法でサイクル検出器を無効にすると、 gc モジュールは使用できなくなります。

1.10.1。 Pythonでの参照カウント

Py_INCREF(x)Py_DECREF(x)の2つのマクロがあり、参照カウントのインクリメントとデクリメントを処理します。 Py_DECREF()も、カウントがゼロに達したときにオブジェクトを解放します。 柔軟性を高めるために、free()を直接呼び出すのではなく、オブジェクトの型オブジェクトの関数ポインターを介して呼び出します。 この目的(およびその他)のために、すべてのオブジェクトには、その型オブジェクトへのポインターも含まれています。

Py_INCREF(x)Py_DECREF(x)をいつ使用するかという大きな問題が残っています。 まず、いくつかの用語を紹介しましょう。 オブジェクトを「所有」する人は誰もいません。 ただし、オブジェクトへの参照所有することはできます。 オブジェクトの参照カウントは、オブジェクトへの所有されている参照の数として定義されるようになりました。 参照の所有者は、参照が不要になったときに Py_DECREF()を呼び出す責任があります。 参照の所有権を譲渡することができます。 所有されている参照を破棄するには、渡す、保存する、または Py_DECREF()を呼び出す3つの方法があります。 所有されている参照を破棄するのを忘れると、メモリリークが発生します。

オブジェクトへの参照を借用 2 することもできます。 参照の借り手は Py_DECREF()を呼び出さないでください。 借り手は、借りた所有者より長くオブジェクトを保持してはなりません。 所有者が参照を破棄した後に借用した参照を使用すると、解放されたメモリを使用するリスクがあるため、完全に回避する必要があります 3

参照を所有するよりも借用することの利点は、コード内のすべての可能なパスで参照を破棄する必要がないことです。つまり、借用した参照を使用すると、次の場合にリークのリスクが発生しません。時期尚早の出口が取られます。 所有よりも借用の不利な点は、借用した所有者が実際にそれを処分した後に、一見正しいコードで借用した参照を使用できる微妙な状況があることです。

Py_INCREF()を呼び出すことにより、借用した参照を所有済みの参照に変更できます。 これは、参照が借用された所有者のステータスには影響しません。新しい所有参照が作成され、完全な所有者の責任が与えられます(新しい所有者は、前の所有者と同様に参照を適切に破棄する必要があります)。


1.10.2。 所有権規則

オブジェクト参照が関数に渡されたり関数から渡されたりするときはいつでも、所有権が参照とともに転送されるかどうかは、関数のインターフェース仕様の一部です。

オブジェクトへの参照を返すほとんどの関数は、参照とともに所有権を渡します。 特に、 PyLong_FromLong()Py_BuildValue()など、新しいオブジェクトを作成する関数を持つすべての関数は、所有権をレシーバーに渡します。 オブジェクトが実際に新しいものでなくても、そのオブジェクトへの新しい参照の所有権を受け取ります。 たとえば、 PyLong_FromLong()は一般的な値のキャッシュを維持し、キャッシュされたアイテムへの参照を返すことができます。

PyObject_GetAttrString()など、他のオブジェクトからオブジェクトを抽出する多くの関数も、参照とともに所有権を譲渡します。 ただし、いくつかの一般的なルーチンは例外であるため、図はあまり明確ではありません: PyTuple_GetItem()PyList_GetItem()PyDict_GetItem()、および[ X172X] PyDict_GetItemString()はすべて、タプル、リスト、またはディクショナリから借用した参照を返します。

関数 PyImport_AddModule()は、返すオブジェクトを実際に作成する場合でも、借用した参照を返します。これは、オブジェクトへの所有参照がsys.modulesに格納されているために可能です。

オブジェクト参照を別の関数に渡すと、通常、関数は参照を借用します。保存する必要がある場合は、 Py_INCREF()を使用して独立した所有者になります。 このルールには、 PyTuple_SetItem()PyList_SetItem()の2つの重要な例外があります。 これらの関数は、失敗した場合でも、渡されたアイテムの所有権を引き継ぎます。 ( PyDict_SetItem()とその友人は所有権を引き継がないことに注意してください—彼らは「正常」です。)

C関数がPythonから呼び出されると、呼び出し元から引数への参照を借用します。 呼び出し元はオブジェクトへの参照を所有しているため、借用された参照の存続期間は、関数が戻るまで保証されます。 そのような借用された参照を保存または渡す必要がある場合にのみ、 Py_INCREF()を呼び出して所有参照に変換する必要があります。

Pythonから呼び出されるC関数から返されるオブジェクト参照は、所有権のある参照である必要があります。所有権は関数からその呼び出し元に転送されます。


1.10.3。 薄い氷

借用した参照を無害に使用すると問題が発生する可能性がある状況がいくつかあります。 これらはすべて、インタプリタの暗黙的な呼び出しと関係があり、参照の所有者がそれを破棄する可能性があります。

知っておくべき最初の最も重要なケースは、リストアイテムへの参照を借用しているときに、無関係のオブジェクトで Py_DECREF()を使用することです。 例えば:

void
bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);

    PyList_SetItem(list, 1, PyLong_FromLong(0L));
    PyObject_Print(item, stdout, 0); /* BUG! */
}

この関数は、最初にlist[0]への参照を借用し、次にlist[1]を値0に置き換え、最後に借用した参照を出力します。 無害に見えますよね? しかし、そうではありません!

PyList_SetItem()への制御フローをたどってみましょう。 リストにはすべてのアイテムへの参照が含まれているため、アイテム1を交換する場合は、元のアイテム1を破棄する必要があります。 ここで、元の項目1がユーザー定義クラスのインスタンスであり、さらにそのクラスが__del__()メソッドを定義したと仮定しましょう。 このクラスインスタンスの参照カウントが1の場合、それを破棄すると、__del__()メソッドが呼び出されます。

Pythonで記述されているため、__del__()メソッドは任意のPythonコードを実行できます。 bug()itemへの参照を無効にするために何かできるでしょうか。 あなたは賭けます! bug()に渡されたリストが__del__()メソッドにアクセス可能であると仮定すると、del list[0]の効果のステートメントを実行でき、これがそのオブジェクトへの最後の参照であると仮定します。 、それに関連付けられたメモリを解放し、それによってitemを無効にします。

問題の原因がわかれば、解決策は簡単です。参照カウントを一時的にインクリメントします。 関数の正しいバージョンは次のとおりです。

void
no_bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);

    Py_INCREF(item);
    PyList_SetItem(list, 1, PyLong_FromLong(0L));
    PyObject_Print(item, stdout, 0);
    Py_DECREF(item);
}

これは実話です。 古いバージョンのPythonにはこのバグの亜種が含まれており、誰かがCデバッガーでかなりの時間を費やして、__del__()メソッドが失敗する理由を突き止めました…

借用された参照に関する問題の2番目のケースは、スレッドを含むバリアントです。 通常、Pythonインタープリターの複数のスレッドは、Pythonのオブジェクト空間全体を保護するグローバルロックがあるため、お互いの邪魔をすることはできません。 ただし、マクロ Py_BEGIN_ALLOW_THREADS を使用してこのロックを一時的に解放し、 Py_END_ALLOW_THREADS を使用して再取得することは可能です。 これは、I / O呼び出しのブロックに関して一般的であり、I / Oが完了するのを待っている間に他のスレッドがプロセッサを使用できるようにします。 明らかに、次の関数には前の関数と同じ問題があります。

void
bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);
    Py_BEGIN_ALLOW_THREADS
    ...some blocking I/O call...
    Py_END_ALLOW_THREADS
    PyObject_Print(item, stdout, 0); /* BUG! */
}

1.10.4。 NULLポインタ

一般に、オブジェクト参照を引数として受け取る関数は、それらにNULLポインターを渡すことを期待せず、そうするとコアをダンプします(または後でコアダンプを引き起こします)。 オブジェクト参照を返す関数は通常、例外が発生したことを示すためにのみNULLを返します。 NULL引数をテストしない理由は、関数が受け取ったオブジェクトを他の関数に渡すことが多いためです。各関数がNULLをテストする場合、冗長なテストが多数発生し、コードの実行速度が遅くなります。

NULLは、「source:」でのみテストすることをお勧めします。たとえば、malloc()から、または例外を発生させます。

マクロ Py_INCREF()および Py_DECREF()は、NULLポインターをチェックしませんが、それらのバリアント Py_XINCREF()および Py_XDECREF()は行います。

特定のオブジェクトタイプ(Pytype_Check())をチェックするためのマクロは、NULLポインターをチェックしません。繰り返しますが、さまざまなものに対してオブジェクトをテストするために、これらのいくつかを連続して呼び出すコードがたくさんあります。予想されるタイプが異なり、これにより冗長なテストが生成されます。 NULLがチェックされているバリアントはありません。

C関数呼び出しメカニズムは、C関数(例ではargs)に渡される引数リストがNULLになることはないことを保証します。実際、常にタプル 4 であることを保証します。 ]。

NULLポインターをPythonユーザーに「エスケープ」させることは重大なエラーです。


1.11。 C ++で拡張機能を書く

C ++で拡張モジュールを作成することは可能です。 いくつかの制限が適用されます。 メインプログラム(Pythonインタープリター)がCコンパイラーによってコンパイルおよびリンクされている場合、コンストラクターを持つグローバルオブジェクトまたは静的オブジェクトは使用できません。 メインプログラムがC ++コンパイラによってリンクされている場合、これは問題ではありません。 Pythonインタープリターによって呼び出される関数(特にモジュール初期化関数)は、extern "C"を使用して宣言する必要があります。 Pythonヘッダーファイルをextern "C" {...}で囲む必要はありません—シンボル__cplusplusが定義されている場合(最近のすべてのC ++コンパイラがこのシンボルを定義している場合)、Pythonヘッダーファイルはすでにこの形式を使用しています。


1.12。 拡張モジュール用のCAPIの提供

多くの拡張モジュールは、Pythonから使用する新しい関数と型を提供するだけですが、拡張モジュールのコードが他の拡張モジュールに役立つ場合があります。 たとえば、拡張モジュールは、順序のないリストのように機能するタイプ「コレクション」を実装できます。 標準のPythonリストタイプに拡張モジュールがリストを作成および操作できるようにするCAPIがあるのと同様に、この新しいコレクションタイプには、他の拡張モジュールから直接操作するためのC関数のセットが必要です。

一見、これは簡単に思えます。関数を記述し(もちろん、staticを宣言せずに)、適切なヘッダーファイルを提供し、CAPIを文書化します。 実際、これは、すべての拡張モジュールが常にPythonインタープリターと静的にリンクされている場合に機能します。 ただし、モジュールを共有ライブラリとして使用する場合、あるモジュールで定義されたシンボルが別のモジュールに表示されない場合があります。 可視性の詳細は、オペレーティングシステムによって異なります。 一部のシステムはPythonインタープリターとすべての拡張モジュール(Windowsなど)に1つのグローバル名前空間を使用しますが、他のシステムはモジュールリンク時にインポートされたシンボルの明示的なリストを必要とします(AIXは1つの例です)、またはさまざまな戦略の選択を提供します(ほとんどユニス)。 また、シンボルがグローバルに表示されている場合でも、呼び出したい関数のモジュールがまだロードされていない可能性があります。

したがって、移植性では、シンボルの可視性について何も仮定する必要はありません。 これは、他の拡張モジュールとの名前の衝突を避けるために、モジュールの初期化関数を除いて、拡張モジュール内のすべてのシンボルをstaticと宣言する必要があることを意味します(セクションモジュールのメソッドテーブルと初期化関数で説明) )。 また、他の拡張モジュールからにアクセスできる必要があるシンボルは、別の方法でエクスポートする必要があることを意味します。

Pythonは、ある拡張モジュールから別の拡張モジュールにCレベルの情報(ポインター)を渡すための特別なメカニズムであるカプセルを提供します。 Capsuleは、ポインター( void * )を格納するPythonデータ型です。 カプセルは、C APIを介してのみ作成およびアクセスできますが、他のPythonオブジェクトと同様に渡すことができます。 特に、拡張モジュールの名前空間内の名前に割り当てることができます。 その後、他の拡張モジュールはこのモジュールをインポートし、この名前の値を取得してから、カプセルからポインターを取得できます。

カプセルを使用して拡張モジュールのCAPIをエクスポートする方法はたくさんあります。 各関数は独自のCapsuleを取得することも、すべてのC APIポインターを、アドレスがCapsuleで公開されている配列に格納することもできます。 また、ポインタを格納および取得するさまざまなタスクは、コードを提供するモジュールとクライアントモジュールの間でさまざまな方法で分散できます。

どちらの方法を選択する場合でも、カプセルに適切な名前を付けることが重要です。 関数 PyCapsule_New()は、名前パラメーター( const char * )を取ります。 NULLの名前を渡すことは許可されていますが、名前を指定することを強くお勧めします。 適切な名前のカプセルは、ある程度の実行時の型安全性を提供します。 名前のないカプセルを別のカプセルと区別するための実行可能な方法はありません。

特に、C APIを公開するために使用されるカプセルには、次の規則に従って名前を付ける必要があります。

modulename.attributename

便利な関数 PyCapsule_Import()を使用すると、Capsuleを介して提供されるC APIを簡単にロードできますが、Capsuleの名前がこの規則に一致する場合に限ります。 この動作により、C APIユーザーは、ロードするカプセルに正しいCAPIが含まれていることを確信できます。

次の例は、エクスポートモジュールの作成者にほとんどの負担をかけるアプローチを示しています。これは、一般的に使用されるライブラリモジュールに適しています。 すべてのCAPIポインター(例では1つだけ!)を void ポインターの配列に格納します。これは、カプセルの値になります。 モジュールに対応するヘッダーファイルは、モジュールのインポートとそのCAPIポインターの取得を処理するマクロを提供します。 クライアントモジュールは、CAPIにアクセスする前にこのマクロを呼び出すだけで済みます。

エクスポートモジュールは、セクション A Simple Examplespamモジュールを変更したものです。 関数spam.system()は、Cライブラリ関数system()を直接呼び出すのではなく、関数PySpam_System()を呼び出します。これは、もちろん実際にはもっと複雑なことを行います(「スパム」の追加など)。すべてのコマンドに)。 この関数PySpam_System()は、他の拡張モジュールにもエクスポートされます。

関数PySpam_System()はプレーンなC関数であり、他のすべてと同様にstaticと宣言されています。

static int
PySpam_System(const char *command)
{
    return system(command);
}

関数spam_system()は簡単な方法で変更されます。

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = PySpam_System(command);
    return PyLong_FromLong(sts);
}

モジュールの最初、行の直後

#include <Python.h>

さらに2行追加する必要があります。

#define SPAM_MODULE
#include "spammodule.h"

#defineは、クライアントモジュールではなく、エクスポートモジュールに含まれていることをヘッダーファイルに通知するために使用されます。 最後に、モジュールの初期化関数は、CAPIポインター配列の初期化を処理する必要があります。

PyMODINIT_FUNC
PyInit_spam(void)
{
    PyObject *m;
    static void *PySpam_API[PySpam_API_pointers];
    PyObject *c_api_object;

    m = PyModule_Create(&spammodule);
    if (m == NULL)
        return NULL;

    /* Initialize the C API pointer array */
    PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;

    /* Create a Capsule containing the API pointer array's address */
    c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL);

    if (PyModule_AddObject(m, "_C_API", c_api_object) < 0) {
        Py_XDECREF(c_api_object);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

PySpam_APIstaticと宣言されていることに注意してください。 そうしないと、PyInit_spam()が終了したときにポインタ配列が消えてしまいます!

作業の大部分は、次のようなヘッダーファイルspammodule.hにあります。

#ifndef Py_SPAMMODULE_H
#define Py_SPAMMODULE_H
#ifdef __cplusplus
extern "C" {
#endif

/* Header file for spammodule */

/* C API functions */
#define PySpam_System_NUM 0
#define PySpam_System_RETURN int
#define PySpam_System_PROTO (const char *command)

/* Total number of C API pointers */
#define PySpam_API_pointers 1


#ifdef SPAM_MODULE
/* This section is used when compiling spammodule.c */

static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;

#else
/* This section is used in modules that use spammodule's API */

static void **PySpam_API;

#define PySpam_System \
 (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])

/* Return -1 on error, 0 on success.
 * PyCapsule_Import will set an exception if there's an error.
 */
static int
import_spam(void)
{
    PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0);
    return (PySpam_API != NULL) ? 0 : -1;
}

#endif

#ifdef __cplusplus
}
#endif

#endif /* !defined(Py_SPAMMODULE_H) */

関数PySpam_System()にアクセスするためにクライアントモジュールが実行する必要があるのは、初期化関数で関数(またはマクロ)import_spam()を呼び出すことだけです。

PyMODINIT_FUNC
PyInit_client(void)
{
    PyObject *m;

    m = PyModule_Create(&clientmodule);
    if (m == NULL)
        return NULL;
    if (import_spam() < 0)
        return NULL;
    /* additional initialization can happen here */
    return m;
}

このアプローチの主な欠点は、ファイルspammodule.hがかなり複雑なことです。 ただし、基本的な構造はエクスポートされる各関数で同じであるため、一度だけ学習する必要があります。

最後に、カプセルは追加の機能を提供します。これは、カプセルに格納されているポインタのメモリ割り当てと割り当て解除に特に役立ちます。 詳細については、Python / CAPIリファレンスマニュアルの Capsules セクションおよびCapsulesの実装(PythonソースコードディストリビューションのファイルInclude/pycapsule.hおよびObjects/pycapsule.c)に記載されています。 。

脚注

1
この機能のインターフェースは、標準モジュール os にすでに存在します—これは単純でわかりやすい例として選択されました。
2
参照を「借用」するというメタファーは完全には正しくありません。所有者はまだ参照のコピーを持っています。
3
参照カウントが少なくとも1であることを確認するは機能しません —参照カウント自体が解放されたメモリにある可能性があるため、別のオブジェクトに再利用される可能性があります。
4
これらの保証は、「古い」スタイルの呼び出し規約を使用する場合には当てはまりません。これは、既存の多くのコードにまだ見られます。