C または C++ を使用して Python を拡張する

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

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

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

ノート

 

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

1.1. 簡単な例

C ライブラリ関数1へのPython インターフェイスを作成するとspam仮定して、(Monty Python Fans' Favorite Food...) という拡張モジュールを作成しましょう。この関数は、NULL で終わる文字列を引数として受け取り、整数を返します。この関数を次のように Python から呼び出せるようにします。system() 

>>>スパムをインポート >>>ステータス=スパムシステム( "ls -l" )

まずファイルを作成しますspammodule.c(歴史的に、モジュールが呼び出された場合はspam、その実装を含む C ファイルが呼び出されていました spammodule.c。たとえば、モジュール名が長い場合、spammifyモジュール名は単に ということもありましたspammify.c。)

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

#include  <Python.h>

Python API が導入されています (必要に応じて、モジュールの目的を説明するコメントや著作権表示を追加できます)。

ノート

 

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

標準ヘッダー ファイルで定義されているシンボルに加えて、 で定義されているユーザーに表示されるすべてのシンボルには、または というPython.h接頭辞が付けられます。便宜上、また Python インタプリタがこれらを広範囲に使用するため、 いくつかの標準ヘッダーが含まれています: 、 、 および. 後者のヘッダーがシステムに存在しない場合は、関数および を 直接宣言しますPyPY"Python.h"<stdio.h><string.h><errno.h><stdlib.h>malloc()free()realloc()

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

static PyObject * 
spam_system (PyObject * self, PyObject * args)
{ const char *コマンド;
    int st; if ( ! PyArg_ParseTuple(args, "s" , & command))
         return NULL ; 
    sts =システム(コマンド);
    PyLong_FromLong(sts)を返します; 
}
      

     

Python のパラメーター リスト (単一の式など) から C 関数に渡されるパラメーターへの単純な変換があります。C 関数には常に 2 つのパラメーターがあり、通常はself とargsという名前が付けられます。"ls -l"

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

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

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

1.2. 間奏曲: エラーと例外

Python インタプリタ全体にわたる重要な規則は次のとおりです。関数が失敗した場合、例外条件を設定し、エラー値 (通常は NULLポインタ) を返す必要があります。例外はインタープリタ内の静的グローバル変数に格納されます。この変数がNULL の場合、例外は発生しません。2 番目のグローバル変数には、例外の「関連する値」 ( raise の2 番目の引数) が格納されます。3 番目の変数には、エラーが Python コードから発生した場合のスタック トレースバックが含まれます。これら 3 つの変数は、Python の結果に相当する C 言語です ( 『Python ライブラリ リファレンス』のsys.exc_info()モジュールに関するセクションを参照)。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または ) を返す必要があります。いずれかの関数を呼び出すべきではありません。関数の 1 つはすでにgによって呼び出されています。次に、 f の呼び出し元も、呼び出しなどを行わずに、その呼び出し元にエラー表示を返す必要があります。エラーの最も詳細な原因は、最初にエラーを検出した関数によって報告されます。エラーが Python インタプリタのメイン ループに到達すると、現在実行中の Python コードが中止され、Python プログラマによって指定された例外ハンドラの検索が試行されます。-1PyErr_*()PyErr_*()

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

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

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

また、 や などの重要な例外を除き、 Unix システム コールPyArg_ParseTuple()と同様に、整数ステータスを返す関数は通常、成功時と失敗時に正の値またはゼロを返すことにも注意してください。-1

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

どの例外をスローするかの選択は完全にあなた次第です。すべての組み込み Python 例外には、 PyExc_ZeroDivisionErrorたとえば、直接使用できる、対応する事前に宣言された C オブジェクトがあります。もちろん、例外は賢明に選択する必要があります。PyExc_TypeErrorファイルを開くことができないことを示すために例外を使用しないでください (おそらくそうすべきですPyExc_IOError)。PyArg_ParseTuple() 関数は通常、引数 list に問題がある場合に呼び出されますPyExc_TypeErrorPyExc_ValueErrorこれは、値が特定の範囲内である必要があるパラメータがある場合、または他の条件を満たす必要があるパラメータがある場合に 適用されます。

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

静的PyObject * SpamError;

そして、 PyInit_spam()モジュールの初期化関数 () の例外オブジェクトを使用して初期化します (今のところエラー チェックは無視します)。

PyMODINIT_FUNC
 PyInit_spam ( void ) 
{ 
    PyObject * m; 

    m = PyModule_Create( & spammodule);
    if (m ==  NULL )
        はNULLを返します 
    SpamError = PyErr_NewException( "spam.error" , NULL , NULL ); 
    Py_INCREF(スパムエラー); 
    PyModule_AddObject(m, "error" , SpamError);
    mを返します
}

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

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

PyMODINIT_FUNC関数の戻り値の型としての使用については、この例で後ほど説明します。

次のような呼び出しを使用してspam.error拡張モジュールで例外をスローできます: PyErr_SetString()

static PyObject * 
spam_system (PyObject * self, PyObject * args)
{ const char *コマンド;
    int st; if ( ! PyArg_ParseTuple(args, "s" , & command))
         return NULL ; 
    sts =システム(コマンド);
    if (sts < 0 ) {
        PyErr_SetString(SpamError, "システム コマンドが失敗しました" );
        NULLを返します
    }戻ります
      

       
    PyLong_FromLong(sts); 
}

1.3. 例に戻る

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

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

パラメータ リストでエラーが検出された場合は、 PyArg_ParseTuple()に応じてNULL (オブジェクト ポインタを返す関数のエラー インジケータ) が 返されます。それ以外の場合は、パラメータの文字列値がローカル変数にコピーされていますこれはポインタの代入であり、それが指す文字列を変更してはなりません (したがって、標準 C では変数を適切に宣言する必要があります)。commandcommandconst char *command

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

sts =システム(コマンド);

関数がspam.system()Python オブジェクトの形式で返さなければならない値。stsこれは、関数PyLong_FromLong()を使用して行われます。

 PyLong_FromLong (sts)
を返します。

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

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

Py_INCREF(Py_None);
Py_Noneを返します

Py_None は、特別な Python オブジェクトの C 名ですNoneこれは実際の Python オブジェクトであり、NULLポインターではありません。これまで見てきたように、NULL ポインターはほとんどの場合「エラー」を意味します。

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

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

static PyMethodDef SpamMethods[] = {
    ...
    { "system" , spam_system, METH_VARARGS,
      "シェル コマンドを実行します。" },
    ...
    { NULL , NULL , 0 , NULL }         /* センチネル */ 
};
        
        
        
        

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

のみ使用する場合、関数はMETH_VARARGS、解析に許容されるタプルとして PyArg_ParseTuple()に渡される Python レベルの引数を予期する必要があります。この機能の詳細については、以下で説明します。

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

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

static  struct PyModuleDef spammodule = {
    PyModuleDef_HEAD_INIT, "spam" ,    /* モジュールの名前 */ 
    spam_doc, /* モジュールのドキュメント、NULL の場合もあります */ - 1 ,        /* モジュールのインタープリターごとの状態のサイズ、 または -1 の場合モジュールは状態をグローバル変数に保持します。*/ 
    SpamMethods
};
    
    

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

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

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

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

Python を埋め込む場合、PyInit_spam()テーブルにエントリが存在しない限り、この関数は自動的に呼び出されませんPyImport_Inittab初期化テーブルにモジュールを追加するには、PyImport_AppendInittab()を使用し、必要に応じてモジュールのインポートを続けます。

int 
main ( int argc, char  * argv[]) 
{ wchar_t * Program = Py_DecodeLocale(argv[ 0 ], NULL );
    if (program == NULL ) { 
        fprintf(stderr, "致命的なエラー: argv[0] をデコードできません\n " ); 
        exit( 1 ); 
    } /* Py_Initialize の前に組み込みモジュールを追加します */ 
    PyImport_AppendInittab( "spam" , PyInit_spam); /* argv[0] を Python インタープリターに渡します */
      

    

    
    Py_SetProgramName(プログラム); /* Python インタープリターを初期化します。必須。*/ 
    Py_Initialize(); /* 必要に応じてモジュールをインポートします。あるいは、 埋め込みスクリプトが インポートするまでインポートを延期することもできます。*/ 
    PyImport_ImportModule( "スパム" ); 
    ... 
    PyMem_RawFree(プログラム); 0を返します
}

    

    




     

ノート

 

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

より重要なサンプル モジュールは、 という名前の Python ソース配布に含まれておりModules/xxmodule.c、このファイルはテンプレートとして使用することも、単にサンプルとして読み取ることもできます。

ノート

 

このspam例とは異なり、マルチフェーズ初期化xxmodule(Python 3.5 の新機能)が使用されており 、そこから PyModuleDef 構造体を返し 、モジュールの作成をインポート メカニズムに任せます。マルチフェーズ初期化の詳細については、PEP 489を参照してください。PyInit_spam

1.5. コンパイルとリンク

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

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

スパム spammodule.o

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

モジュールが他のライブラリとリンクする必要がある場合、これらのライブラリを構成ファイル内の行にリストすることもできます。次に例を示します。

スパム spammodule.o -lX11

1.6. C からの Python 関数の呼び出し

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

幸いなことに、Python インタープリターは再帰的に呼び出すのが簡単で、Python 関数を呼び出すための標準インターフェイスを備えています。(特定の文字列を入力として Python パーサーを呼び出す方法については詳しく説明しません。興味がある場合は、Python ソース コードの-cコマンド ライン オプションの実装を確認してください。)Modules/main.c

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

静的PyObject * my_callback =  NULL ; static PyObject * my_set_callback (PyObject *ダミー, PyObject * args)
{
    PyObject * result = NULL ; 
    PyObject * temp; if (PyArg_ParseTuple(args, "O:set_callback" , & temp)) {
         if ( ! PyCallable_Check(temp)) {
            PyErr_SetString(PyExc_TypeError, "パラメータは呼び出し可能である必要があります" );
            戻る


 

     NULL ; 
        Py_XINCREF 
        (一時);         /* 新しいコールバックへの参照を追加します */ 
        Py_XDECREF(my_callback);  /* 以前のコールバックを破棄します */ 
        my_callback = temp;       /* 新しいコールバックを記憶します */ 
        /* "None" を返すボイラープレート */ 
        Py_INCREF(Py_None); 
        結果= Py_None; 結果を返します
}
    

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

マクロPy_XINCREF()およびPy_XDECREF() は、オブジェクトの参照カウントをインクリメント/デクリメントし、NULLポインターの存在下でも安全です(ただし、このコンテキストではtempは NULLではない ことに注意してください)。詳細については、「参照カウント」セクションを参照してください。

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

整数引数; 
PyObject * arglist; 
PyObject *結果; 
...
引数=  123 ; 
... /* コールバックを呼び出す時間 */ 
arglist = Py_BuildValue( "(i)" , arg); 
結果= PyObject_CallObject(my_callback, arglist); 
Py_DECREF(arglist);

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

PyObject_CallObject()の戻り値は「new」です。つまり、完全に新しいオブジェクト、または参照カウントが増加した既存のオブジェクトのいずれかです。したがって、グローバル変数に保存したくない場合は、その値に興味がない場合でも (特に!)何らかの方法でPy_DECREF()の結果を取得する必要があります。

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

if (result ==  NULL )
    はNULLを返します /* エラーを返します */ 
...結果を使用...
Py_DECREF(result);

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

PyObject * arglist; 
... 
arglist = Py_BuildValue( "(l)" , イベントコード); 
結果= PyObject_CallObject(my_callback, arglist); 
Py_DECREF(arglist); if (result == NULL )
    はNULLを返します/* エラーを返します */ /* ここで結果を使用するかもしれません */ 
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 )
    はNULLを返します/* エラーを返します */ /* ここで結果を使用するかもしれません */ 
Py_DECREF(result);
  

1.7. 拡張関数内のパラメータの抽出

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

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

argパラメータは、Python から C 関数に渡される引数のリストを含むタプル オブジェクトである必要があります。format パラメーターはフォーマット文字列である必要があります。その構文については、『Python/C API Reference Manual』の「Parsing Parameters and Building Values」で説明されています残りの引数は、形式文字列によって型が決定される変数のアドレスでなければなりません。

PyArg_ParseTuple()は Python 引数が必要な型であることをチェックしますが、呼び出しに渡される C 変数アドレスの有効性はチェックできないことに注意してください。ここで間違いを犯すと、コードがクラッシュするか、少なくともランダム ビットが上書きされる可能性があります記憶の中で。ので注意してください!

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

呼び出しの例:

#define PY_SSIZE_T_CLEAN /* 「s#」に int ではなく Py_ssize_t を使用させます。*/
 #include  <Python.h>
int ok;
int i, j;
長いk、l。
const  char  * s; 
Py_ssize_t サイズ; 

ok = PyArg_ParseTuple(args, "" ); /* 引数なし */ 
    /* Python 呼び出し: f() */
ok = PyArg_ParseTuple(args, "s" , & s); /* 文字列 */ 
    /* 可能な Python 呼び出し: f('whoops!') */
ok = PyArg_ParseTuple(args, "lls" , & k, & l, & s); /* 2 つの long と 1 つの文字列 */ 
    /* 可能な Python 呼び出し: f(1, 2, 'three') */
ok = PyArg_ParseTuple(args, "(ii)s#" , & i, & j, & s, & size);
    /* int と string のペア。そのサイズも返されます */ 
    /* 可能な Python 呼び出し: f((1, 2), 'three') */
{
     const  char  *ファイル;
    const  char  *モード=  "r" ;
    int bufsize =  0 ; 
    ok = PyArg_ParseTuple(args, "s|si" , & file, & mode, & bufsize);
    /* 文字列、およびオプションで別の文字列と整数 */ / 
    * 可能な Python 呼び出し: 
 f('spam') 
 f('spam', 'w') 
 f('spam', 'wb', 100000) */ 
}
{
     int左、上、右、下、h、v; 
    ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)" ,
              & left, & top, & right, & bottom, & h, & v);
    /* 長方形と点 */ 
    /* 可能な Python 呼び出し: 
 f(((0, 0), (400, 300)), (10, 10)) */ 
}
{ 
    Py_complex c; 
    ok = PyArg_ParseTuple(args, "D:myfunction" , & c);
    /* 複合体。エラーの関数名も提供します */ 
    /* 可能な Python 呼び出し: 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 で終了する文字列のリストで、名前は左から右の形式で型情報と一致します。PyArg_ParseTupleAndKeywords()は、成功した場合はtrue を返し、それ以外の場合は false を返し、適切な例外を発生させます。

ノート

 

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

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

#include  "Python.h" 

static PyObject * 
keywdarg_parrot (PyObject * self, PyObject * args, PyObject * keywds) 
{ int Voltage;
    char * state = "硬い" ;
    char *アクション= "ブーム" ;
    char * type = "ノルウェージャン ブルー" ; static char * kwlist[] = {
         "電圧" , "状態" ,
          

      
        "タイプ" , NULL }; if ( ! PyArg_ParseTupleAndKeywords(args, keywds, "i|sss" , kwlist,
                                      &電圧, &状態, &アクション, &タイプ))
         return NULL ; 
    printf( "-- このオウムは %i ボルトを流しても %s しません。\n "
           アクション、電圧); 
    printf( "-- 素敵な羽毛、%s -- %s です! \n " , type, state); 
    Py_RETURN_NONE; 
静的_

     



PyMethodDef keywdarg_methods[] = {
     /* PyCFunction 値は * 2 つの PyObject* パラメータのみを受け取り、keywdarg_parrot() は * 3 つのパラメータを取るため、関数のキャストが必要です
 */ 
    { "オウム" , (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
      "素敵な寸劇を標準出力に出力します。" }, 
    { NULL , NULL , 0 , NULL }    /* センチネル */ 
}; 静的構造体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("") なし
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) '地獄' 
Py_BuildValue("y#", "hello", 4) まったく' 
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()関数 sum を使用して行われますfree()newC++ では、演算子と の使用はdelete本質的に同じ意味を持ち、以下の説明は C の場合に限定します。

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

メモリ リークの一般的な原因は、コード内の異常なパスです。たとえば、関数はメモリのブロックを割り当て、いくつかの計算を実行し、その後ブロックを再度解放する場合があります。関数の要件を変更すると、エラー条件を検出して関数から早期に復帰できるテストを計算に追加できるようになりました。この種の早期終了を行う場合、特に後からコードに追加した場合には、割り当てられたメモリ ブロックを解放することを忘れがちです。このようなリークは一度発生すると、長期間検出されないことがよくあります: エラー終了はすべての呼び出しのほんの一部でのみ発生し、最近のコンピューターのほとんどには十分な仮想メモリがあるため、リークは長期間しか発生しません。リーク機能を使用するときにプロセスが頻繁に実行される時間。それで、

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

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

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

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

1.10.1. Python での参照カウント

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

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

 object2 への参照を借用することもできます参考資料の借用者はPy_DECREF()を呼び出すべきではありません。借り手は、貸与されたアイテムの所有者よりも長くアイテムを保持することはできません。所有者が破棄した後に借用した参照を使用することは、解放されたメモリを使用するリスクを伴うため、完全に避けるべきです3

参照を所有することよりも参照を借用することの利点は、コード内のすべての可能なパスで参照を処理する必要がないことです。つまり、早期に終了するときに借用した参照を使用できます。借用と所有の欠点は、一部の微妙なケースでは、一見正しいコードでも、借用した参照の所有者が実際に破棄した後に借用した参照が使用できることです。

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

1.10.2. 所有権ルール

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

オブジェクト参照を返すほとんどの関数は、参照によって所有権を渡します。特に、新しいオブジェクトを作成する機能を持つすべての関数 ( PyLong_FromLong()Py_BuildValue() など) は、受信者に所有権を渡します。オブジェクトが実際には新しくない場合でも、オブジェクトへの新しい参照の所有権を取得できます。たとえば、  PyLong_FromLong() は一般的な値のキャッシュを維持し、キャッシュされた項目への参照を返すことができます。

他のオブジェクトからオブジェクトを抽出する多くの関数も、 PyObject_GetAttrString()など、参照によって所有権を譲渡しますただし、一部の一般的なルーチンは例外であるため、ここでは状況があまり明確ではありません。  PyTuple_GetItem()PyList_GetItem()PyDict_GetItem() 、および PyDict_GetItemString()はすべて、タプル、リスト、または辞書から借用した参照を返します。

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

オブジェクト参照を別の関数に渡すとき、通常、その関数は参照を借用します。オブジェクト参照を保存する必要がある場合、その関数は Py_INCREF()の独立した所有者になります。このルールには 2 つの重要な例外があります: PyTuple_SetItem()と PyList_SetItem()です。これらの関数は、たとえ失敗したとしても、渡された項目の所有権を取得します。( 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(リスト, 1 , PyLong_FromLong( 0L )); 
    PyObject_Print(item, stdout, 0 ); /* バグ!*/ 
}

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

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

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

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

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

    Py_INCREF(アイテム); 
    PyList_SetItem(リスト, 1 , PyLong_FromLong( 0L )); 
    PyObject_Print(item, stdout, 0 ); 
    Py_DECREF(アイテム); 
}

これは本当の話です。古いバージョンの 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 
    ...I / O 呼び出しをブロックしています... 
    Py_END_ALLOW_THREADS 
    PyObject_Print(item, stdout, 0 ); /* バグ!*/ 
}

1.10.4。ヌルポインタ

一般に、オブジェクト参照を引数として受け取る関数では、 NULLポインターを渡すことは望ましくありません。NULLポインターを渡すと、コアがダンプされます (または、後でコア ダンプが発生します)。オブジェクト参照を返す関数は通常、単に例外が発生したことを示すためにNULLを返します。NULL 引数をテストしない理由は、関数は通常、受け取ったオブジェクトを他の関数に渡すためです。すべての関数がNULLについてテストすると、冗長なテストが多くなり、コードの実行が遅くなります。

NULL かどうかをテストするのは、「ソース」でのみ行うのが最善ですたとえば、例外をスローする可能性のある関数から、または関数へのNULLである可能性のあるポインターを受信する場合です。malloc()

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

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

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

NULLポインタを Python ユーザーに「逃がす」のは重大な間違いです。

1.11. C++ での拡張機能の作成

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

1.12. 拡張モジュール用の C API を提供する 

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

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

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

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

Capsule は、さまざまな方法で拡張モジュールの C API をエクスポートできます。各関数は独自の Capsule を持つことも、すべての C API ポインターを、Capsule でアドレスが公開される配列に格納することもできます。また、ポインタの保存と取得のさまざまなタスクは、コードを提供するモジュールとクライアント モジュール間でさまざまな方法で分散できます。

どの方法を選択する場合でも、Capsule に正しい名前を付けることが重要です。関数PyCapsule_New() は名前引数 ( ) を受け取ります。NULL 名を渡すこともできますが、名前を指定することを強くお勧めします。適切に名前が付けられたカプセルは、実行時の型の安全性をある程度確保しますが、名前のないカプセルを別のカプセルから区別する実現可能な方法はありません。const char *

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

モジュール名.属性名 

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

次の例は、エクスポートされたモジュールの作成者に負担の大部分を与え、一般的に使用されるライブラリ モジュールで機能するアプローチを示しています。すべての C API ポインター (この例では 1 つだけ!) をポインターの配列に格納し、voidこれが Capsule の値になります。モジュールに対応するヘッダー ファイルは、モジュールをインポートし、その C API ポインタを取得するマクロを提供します。クライアント モジュールは、C API にアクセスする前にこのマクロを呼び出すだけで済みます。

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

この関数はPySpam_System()通常の C 関数であり、 static他の関数と同様に宣言されます。

static  int 
PySpam_System ( const  char  *コマンド) { return system(コマンド);  }
    

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

static PyObject * 
spam_system (PyObject * self, PyObject * args) { const char *コマンド; int st; if ( ! PyArg_ParseTuple(args, "s" , & command))  return NULL ;  sts = PySpam_System(コマンド); PyLong_FromLong(sts)を返します;  }
      

     

モジュールの先頭、行の直後

#include  "Python.h"

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

#SPAM_MODULE を定義
#include  "spammodule.h"

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

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 ) はNULLを返します /* C API ポインタ配列を初期化します */ PySpam_API[PySpam_System_NUM] = ( void * )PySpam_System; /* API ポインタ配列のアドレスを含む Capsule を作成します */

     

     c_api_object = PyCapsule_New(( void  * )PySpam_API, "spam._C_API" , NULL ); if (c_api_object != NULL )  PyModule_AddObject(m, "_C_API" , c_api_object); mを返します }

     

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

以下に示すように、ほとんどの作業はヘッダー ファイル内にありますspammodule.h

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

/* スパムモジュールのヘッダー ファイル */ 

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

/* C API ポインターの総数 */
 #define PySpam_API_pointers 1 


#ifdef SPAM_MODULE 
/* このセクションは spammodule.c のコンパイル時に使用されます */ 

static PySpam_System_RETURN PySpam_System PySpam_System_PROTO; #else /* このセクションは、spammodule の API を使用するモジュールで使用されます */ static void ** PySpam_API;




  

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

/* エラーの場合は -1、成功の場合は 0 を返します。
* エラーが発生した場合、PyCapsule_Import は例外を設定します。
*/ 
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 /* !define(Py_SPAMMODULE_H) */

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

PyMODINIT_FUNC PyInit_client ( void )  {  PyObject * m; 
 m = PyModule_Create( & clientmodule); if (m ==  NULL ) はNULLを返します if (import_spam() < 0 ) はNULLを返します/* ここで追加の初期化を行うことができます */ return m;  }  
    

このアプローチの主な欠点は、ファイルがspammodule.h非常に複雑であることです。ただし、各派生関数の基本構造は同じであるため、学習する必要があるのは 1 回だけです。

最後に、カプセルは、カプセルに格納されたポインタのメモリ割り当てと割り当て解除に特に役立つ追加機能を提供することに言及する必要があります。詳細については、Python/C API リファレンス マニュアルのCapsuleセクションと Capsule の実装 (ドキュメント Include/pycapsule.hおよびPython ソース コード配布)Objects/pycapsule.cで説明されています。

おすすめ

転載: blog.csdn.net/tianqiquan/article/details/133385121