拡張モジュールの定義

CPython の C 拡張は共有ライブラリです (例えば Linux の .so ファイルや Windows の .pyd DLL)。 Python プロセスにロードできる必要があり (例えば互換性のあるコンパイラ設定でコンパイルされていること)、エクスポートフック 関数 (または旧形式の 初期化関数) をエクスポートします。

デフォルトで (つまり、 importlib.machinery.ExtensionFileLoader によって) インポート可能であるためには、共有ライブラリは sys.path 上で利用可能である必要があり、モジュール名と importlib.machinery.EXTENSION_SUFFIXES. に含まれる拡張子で命名する必要があります。

注釈

拡張モジュールのビルド、パッケージ化および配布には、サードパーティのツールが最適であり、このドキュメントの範囲外です。適切なツールの一つは Setuptools であり、ドキュメントは https://setuptools.pypa.io/en/latest/setuptools.html にあります。

拡張モジュールのエクスポートフック

Added in version 3.15: PyModExport_<name> エクスポートフックのサポートは Python 3.15 で追加されました。 従来のモジュール定義方法も引き続き利用できます。 以前の Python バージョンに対応する場合は、PyInit 関数 の節または以前のバージョンのドキュメントを参照してください。

エクスポートフックは、次のシグネチャを持つエクスポートされた関数でなければなりません:

PySlot *PyModExport_modulename(void)

名前が ASCII 文字だけのモジュールでは、エクスポートフック の名前は PyModExport_<name> とし、<name> をモジュール名に置き換える必要があります。

モジュール名に非 ASCII 文字が含まれる場合、エクスポートフックの名前は代わりに PyModExportU_<name> とします (U に注意してください)。 <name> には、Python の punycode エンコーディングでエンコードし、ハイフンをアンダースコアに置き換えた名前を使います。 Python では次のようになります:

def hook_name(name):
    try:
        suffix = b'_' + name.encode('ascii')
    except UnicodeEncodeError:
        suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
    return b'PyModExport' + suffix

エクスポートフックは PySlot エントリーの配列を返します。 この配列はスロット ID が 0 のエントリーで終端します。 これらのスロットで、モジュールの作成方法と初期化方法を記述します。

この配列はインタープリタの終了まで有効で、変更されない必要があります。 通常は static な記憶域を使ってください。 動的な処理には Py_mod_create スロットと Py_mod_exec スロットを使うことを推奨します。

エクスポートフックは、例外を設定して NULL を返すことで失敗を通知できます。

エクスポートフック関数の定義には、次の補助マクロを使うことを推奨します:

PyMODEXPORT_FUNC
This macro is available when compiling for the Stable ABI (バージョン 3.15 より).

拡張モジュールのエクスポートフックを宣言します。 このマクロは次の処理を行います:

  • 戻り値の型として PySlot* を指定します。

  • プラットフォームが要求する特別なリンケージ宣言を追加します。

  • C++ では、関数を extern "C" として宣言します。

例えば spam というモジュールは、次のように定義します。

PyABIInfo_VAR(abi_info);

static PySlot spam_slots[] = {
    PySlot_STATIC_DATA(Py_mod_abi, &abi_info),
    PySlot_STATIC_DATA(Py_mod_name, "spam"),
    PySlot_FUNC(Py_mod_init, spam_init_function),
    ...
    PySlot_END
};

PyMODEXPORT_FUNC
PyModExport_spam(void)
{
    return spam_slots;
}

通常、モジュールの C ソースで定義する要素のうち、static でないのはエクスポートフックだけです。

フックの処理は短くしてください。 静的な配列を return する以外の処理を行う場合は、いくつか注意点があります:

  • Python C API を使う必要がある場合は、最初に PyABIInfo_Check() を呼び出すことを推奨します。 これにより、よくある ABI の不一致の場合にクラッシュする代わりに例外を送出できます。

  • エクスポートフックのコードは GIL に決して依存してはいけません。 Python の フリースレッドビルド では、フックから戻った後でなければ Py_mod_gil スロット (またはその不在) を確認できないためです。

  • 同様に、Py_mod_multiple_interpreters スロット (またはその不在) を確認するのはフックから戻った後なので、フックはどのサブインタープリタでも呼び出される可能性があります。

例えば:

PyMODEXPORT_FUNC
PyModExport_modulename(void)
{
   if (PyABIInfo_Check(&abi_info, "modulename") < 0) {
      /* ABI の不一致。送出された例外を調べることは安全ではありません。 */
      return NULL;
   }

   /* Python API はできるだけ使わず、GIL に依存しないこと */

   return modulename_slots;
}

注釈

複数のエクスポートフックを定義すれば、1 つの共有ライブラリから複数のモジュールをエクスポートできます。 ただし、Python のインポート機構はファイル名に対応する関数だけを探すため、これらをインポートするには、独自のインポーターか、適切な名前を付けた拡張ファイルのコピーまたはリンクが必要です。 詳細は PEP 489 の Multiple modules in one library の節を参照してください。

多段階初期化

拡張モジュールの作成はいくつかの段階に分かれています:

  • Python はエクスポートフックを探して呼び出し、モジュールの作成方法に関する情報を取得します。

  • 実質的な処理を行うコードが実行される前に、Python はモジュールが対応する機能を確認し、環境を調整したり、互換性のない拡張モジュールのロードを拒否したりできます。 Py_mod_abi、Py_mod_gil、Py_mod_multiple_interpreters などのスロットがこの段階に影響します。

  • 次に、デフォルトでは Python 自身がモジュールオブジェクトを作成します。 これは、オブジェクトを作成するときの __new__() の呼び出しに相当します。 この段階の処理は Py_mod_create スロットでオーバーライドできます。

  • Python は __package__ や __loader__ などのモジュール属性の初期値を設定し、モジュールオブジェクトを sys.modules に挿入します。

  • その後、拡張モジュール固有の方法でモジュールオブジェクトを初期化します。 これはオブジェクト作成時の __init__() や、Python で書かれたモジュールのトップレベルのコード実行に相当します。 動作は Py_mod_exec スロットで指定します。

これを 多段階初期化 と呼び、初期化関数が完全に構築済みのモジュールを返す従来の 一段階初期化 (現在もサポートされています) と区別します。

バージョン 3.5 で変更: 多段階初期化 (PEP 489) のサポートを追加しました。

複数のモジュールインスタンス

デフォルトでは、拡張モジュールはシングルトンではありません。 例えば sys.modules のエントリーを削除してモジュールを再インポートすると、新しいモジュールオブジェクトを作成し、通常は新しいメソッドオブジェクトや型オブジェクトを設定します。 古いモジュールは通常のガベージコレクションの対象になります。 これは純粋な Python モジュールと同じ動作です。

サブインタープリタ 内や、Python ランタイムの再初期化 (Py_Finalize() と Py_Initialize()) 後にも、追加のモジュールインスタンスが作成される可能性があります。 これらの場合、モジュールインスタンス間で Python オブジェクトを共有すると、クラッシュや未定義の動作を引き起こしやすくなります。

このような問題を避けるには、拡張モジュールの各インスタンスを 分離 する必要があります。 あるインスタンスの変更が暗黙に他のインスタンスに影響してはいけません。 また、Python オブジェクトへの参照を含む、モジュールが所有するすべての状態は、特定のモジュールインスタンスだけのものでなければなりません。 詳細と実践的な手引きは 拡張モジュールを分離する を参照してください。

これらの問題を避けるより簡単な方法は、初期化を繰り返すとエラーを送出する ことです。

すべてのモジュールは サブインタープリタ に対応するか、対応していないことを明示的に通知することが期待されます。 通常は、上述したように分離を行うか、初期化の繰り返しを禁止します。 Py_mod_multiple_interpreters スロットで、モジュールをメインインタープリタに限定することもできます。

PyInit 関数

バージョン 3.15 で ソフト非推奨 <Soft deprecated>: この機能に新しい機能は追加されませんが、削除する予定はありません。

拡張モジュールは PyModExport_modulename() の代わりに、次のシグネチャを持つ旧形式の 初期化関数 を定義できます:

PyObject *PyInit_modulename(void)

名前は PyInit_<name> とし、<name> をモジュール名に置き換えます。 モジュール名に非 ASCII 文字が含まれる場合は、代わりに PyInitU_<name> を使い、<name> を エクスポートフック と同じ方法 (アンダースコアを使う Punycode) でエンコードします。

モジュールが PyInit_<name> と PyModExport_<name> の両方をエクスポートする場合、PyInit_<name> 関数は無視されます。

PyMODEXPORT_FUNC と同様に、初期化関数の定義には補助マクロを使うことを推奨します:

PyMODINIT_FUNC

拡張モジュールの初期化関数を宣言します。 このマクロは次の処理を行います:

  • 戻り値の型として PyObject* を指定します。

  • プラットフォームが要求する特別なリンケージ宣言を追加します。

  • C++ では、関数を extern "C" として宣言します。

通常、初期化関数 (PyInit_modulename) は、m_slots が NULL でない PyModuleDef インスタンスを返します。 これにより Python は 多段階初期化 を利用できます。

返す前に、次の関数を使って PyModuleDef インスタンスを初期化する必要があります:

PyObject *PyModuleDef_Init(PyModuleDef *def)
次に属します: Stable ABI (バージョン 3.5 より).

モジュール定義を、自身の型と参照カウントを正しく示す、適切に初期化された Python オブジェクトにします。

def を PyObject* にキャストして返します。 エラーが発生した場合は NULL を返します。

モジュール初期化関数から PyModuleDef を返す前に、この関数を呼び出す必要があります。 他の場面では使わないでください。

Python は PyModuleDef 構造体が静的に割り当てられていると仮定することに注意してください。 この関数は新しい参照または借用参照を返しますが、その参照を解放してはいけません。

Added in version 3.5.

例えば spam というモジュールは、次のように定義します。

static struct PyModuleDef spam_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "spam",
    ...
};

PyMODINIT_FUNC
PyInit_spam(void)
{
    return PyModuleDef_Init(&spam_module);
}

従来の一段階初期化

バージョン 3.15 で ソフト非推奨 <Soft deprecated>: 一段階初期化は拡張モジュールを初期化する従来の仕組みであり、既知の欠点や設計上の問題があります。 拡張モジュールの作者には、代わりに多段階初期化を使うことを推奨します。

ただし、一段階初期化のサポートを削除する予定はありません。

一段階初期化では、旧形式の 初期化関数 (PyInit_modulename) がモジュールオブジェクトを作成し、内容を設定して返す必要があります。 通常は PyModule_Create() と PyModule_AddObjectRef() などの関数を使います。

一段階初期化は、次の点で デフォルトの方式 と異なります:

  • PyModuleDef.m_size を -1 に設定した一段階初期化モジュールは「シングルトン」です。 より正確には、シングルトンを 含みます。

    このようなモジュールを初めて初期化すると、Python はモジュールの __dict__ の内容 (通常はモジュールの関数と型) を保存します。

    それ以降のインポートでは、Python は初期化関数を再び呼び出しません。 代わりに、新しい __dict__ を持つ新しいモジュールオブジェクトを作成し、保存しておいた内容をコピーします。 モジュールと保存した内容は、次の (module_name, file_name) タプルで対応づけられます:

    • module_name はモジュールのドット区切りの完全な名前で、ModuleSpec.name (またはインポートを行う関数の同等の引数) から取得します。 初期化関数がモジュールの作成に使う名前 (例えば PyModuleDef.m_name) は考慮しないことに注意してください。

    • file_name は拡張モジュールのファイル名です。 組み込みモジュールや、PyImport_CreateModuleFromInitfunc() または PyImport_AppendInittab() で追加したモジュールなど、ファイルからロードしないモジュールでは設定されません。

    初回の初期化の一部として、Python はモジュールをその名前で sys.modules にも追加します。 例えば、関数 sum と例外クラス error を定義する一段階初期化モジュール _testsinglephase [1] を考えます:

    >>> import sys
    >>> import _testsinglephase as one
    >>> del sys.modules['_testsinglephase']
    >>> import _testsinglephase as two
    >>> one is two
    False
    >>> one.__dict__ is two.__dict__
    False
    >>> one.sum is two.sum
    True
    >>> one.error is two.error
    True
    

    厳密な動作は CPython の実装の詳細とみなしてください。

  • PyModuleDef.m_size が非負の一段階初期化モジュールでは、m_size == -1 の場合と同じ (module_name, file_name) キーで、Python が初期化関数へのポインタをキャッシュする場合があります。

    それ以降のインポートで別の初期化関数を指定した場合 (例えば PyImport_CreateModuleFromInitfunc() を使った場合)、キャッシュ済みの関数か、指定した「新しい」関数のどちらかが呼び出される可能性があります。

    この場合も、厳密な動作は CPython の実装の詳細とみなしてください。

  • PyInit_modulename が spec 引数を受け取らないことに対処するため、インポート機構の状態の一部を保存し、PyInit_modulename の呼び出し中に最初に作成された適切なモジュールに適用します。 具体的には、サブモジュールをインポートするとき、この仕組みがモジュール名の先頭に親パッケージ名を付加します。

    一段階初期化の PyInit_modulename 関数は、他のモジュールオブジェクトが作成される前に、できるだけ早く「自身の」モジュールオブジェクトを作成してください。

  • 非 ASCII のモジュール名 (PyInitU_modulename) はサポートされません。

  • 一段階初期化モジュールは、PyState_FindModule() などのモジュール検索関数をサポートします。

  • モジュールの PyModuleDef.m_slots は NULL でなければなりません。