拡張モジュールを分離する
************************


概要
^^^^

従来、Python 拡張モジュールの状態は、プロセス全体をスコープとする C の
"static" 変数に保持されていました。 このドキュメントでは、プロセスごと
の状態が抱える問題と、より安全な方法であるモジュールごとの状態について
説明します。

また、可能な場合にモジュールごとの状態へ移行する方法も説明します。 移
行には状態の領域の割り当て、必要に応じた静的な型からヒープ型への切り替
え、そして特に重要な、コードからモジュールごとの状態へのアクセスが伴い
ます。


対象読者
========

このガイドは、Python 自体をライブラリとして使うアプリケーションでも安
全に利用できる拡張にしたいと考えている、C API 拡張の保守担当者を対象と
しています。


背景
====

*インタープリタ* は Python コードが実行されるコンテキストです。 インポ
ートパスなどの設定と、インポート済みモジュールの集合などの実行時の状態
を含みます。

Python は一つのプロセス内で複数のインタープリタを実行できます。 次の二
つの場合を考える必要があります:

* "Py_InitializeEx()" と "Py_FinalizeEx()" のサイクルを繰り返し、順番
  に実行する場合。

* "Py_NewInterpreter()" と "Py_EndInterpreter()" で「サブインタープリ
  タ」を管理し、並列に実行する場合。

どちらも (また両者の組み合わせも)、Python をライブラリに埋め込む場合に
特に役立ちます。 一般にライブラリは、それを使うアプリケーションについ
て仮定を置くべきではありません。 プロセス全体に一つの「メインの Python
インタープリタ」があるという仮定も同様です。

歴史的に、Python 拡張モジュールはこの用途にうまく対応していません。 C
の "static" 変数が非常に手軽に使えるため、多くの拡張モジュール (標準ラ
イブラリの一部も含みます) は *プロセスごと* のグローバルな状態を使って
います。 その結果、インタープリタ固有であるべきデータがインタープリタ
間で共有されてしまいます。 拡張の開発者が注意しないと、同じプロセスの
複数のインタープリタにモジュールを読み込んだ場合に、特殊な条件でクラッ
シュする問題を簡単に持ち込んでしまいます。

残念ながら、*インタープリタごと* の状態を実現するのは簡単ではありませ
ん。 拡張の開発者は複数のインタープリタを考慮せずに開発しがちで、現在
はその動作のテストにも手間がかかります。


モジュールごとの状態の導入
--------------------------

Python の C API はインタープリタごとの状態よりも、さらに細かい単位の *
モジュールごと* の状態を扱いやすくする方向へ進化しています。 つまり、C
レベルのデータは *モジュールオブジェクト* に結び付けるべきです。 各イ
ンタープリタが独自のモジュールオブジェクトを作成することで、データを分
離します。 分離をテストするために、一つの拡張に対応する複数のモジュー
ルオブジェクトを、一つのインタープリタに読み込むこともできます。

モジュールごとの状態を使うと、寿命とリソースの所有関係を簡単に考えられ
ます。 モジュールオブジェクトの作成時に拡張モジュールを初期化し、解放
時に後始末をします。 この点ではモジュールは他の PyObject* と同じです。
「インタープリタ終了時」のフックを考慮する必要も、それを忘れる心配もあ
りません。

プロセスごと、インタープリタごと、スレッドごと、タスクごとの状態など、
異なる種類の「グローバル」な状態にも用途があります。 モジュールごとの
状態を基本としても、これらを使うことはできます。 ただし例外的な場合と
して扱い、必要なら追加の注意とテストを行ってください。 このガイドでは
これらは扱いません。


分離されたモジュールオブジェクト
--------------------------------

拡張モジュールの開発で押さえるべき点は、一つの共有ライブラリから複数の
モジュールオブジェクトが作成され得るということです。 例えば:

   >>> import sys
   >>> import binascii
   >>> old_binascii = binascii
   >>> del sys.modules['binascii']
   >>> import binascii  # 新しいモジュールオブジェクトを作成
   >>> old_binascii == binascii
   False

原則として、二つのモジュールは完全に独立しているべきです。 モジュール
固有のすべてのオブジェクトと状態をモジュールオブジェクト内にカプセル化
し、他のモジュールオブジェクトと共有せず、モジュールオブジェクトの解放
時に後始末してください。 これはあくまで原則なので例外はあります (グロ
ーバルな状態の管理 を参照)。 ただし、例外的な扱いにはより慎重な検討と
特殊な条件への注意が必要です。

一部のモジュールでは制約を緩められるかもしれません。 それでもモジュー
ルを分離すると、さまざまな用途に通用する明確な期待動作や指針を定めやす
くなります。


意外な動作が生じる場合
----------------------

モジュールを分離すると、一部の場合に意外な動作が生じます。 特に、通常
は各モジュールオブジェクトがクラスや例外を同種の別のモジュールと共有し
ません。 先ほどの例 の続きでは、"old_binascii.Error" と
"binascii.Error" は別のオブジェクトです。 次のコードでは例外は捕捉 *さ
れません*:

   >>> old_binascii.Error == binascii.Error
   False
   >>> try:
   ...     old_binascii.unhexlify(b'qwertyuiop')
   ... except binascii.Error:
   ...     print('boo')
   ...
   Traceback (most recent call last):
     File "<stdin>", line 2, in <module>
   binascii.Error: Non-hexadecimal digit found

これは想定された動作です。 ピュア Python のモジュールも同じように動作
します。 これは Python の仕組みの一部です。

目標は拡張モジュールを C レベルで安全にすることであり、特殊な操作を直
感的に動作させることではありません。 "sys.modules" を「手作業で」変更
するのは、そうした特殊な操作に当たります。


複数のインタープリタでも安全なモジュールにする
==============================================


グローバルな状態の管理
----------------------

Python モジュールに関連する状態が、そのモジュール固有ではなく、プロセ
ス全体 (またはモジュールより「広域」のもの) に属する場合もあります。
例えば:

* "readline" モジュールは、そのプロセスの *端末* を管理します。

* 回路基板で動作するモジュールは、その *基板上の LED* を制御したいかも
  しれません。

このような場合、Python モジュールはグローバルな状態を *所有* するので
はなく、その状態への *アクセス* を提供するべきです。 可能なら、モジュ
ールの複数のコピーが独立して状態にアクセスできるようにしてください
(Python や他の言語用の別のライブラリも同時にアクセスすることがあります
)。 それが不可能なら、明示的なロックを検討してください。

プロセス全体の状態を使う必要がある場合、複数のインタープリタに起因する
問題を避ける最も簡単な方法は、一つのプロセスでモジュールを複数回読み込
めないよう明示的に制限することです。 対応しない場合: プロセスごとに一
つのモジュールオブジェクトに制限する を参照してください。


モジュールごとの状態の管理
--------------------------

モジュールごとの状態を使うには、拡張モジュールの複数フェーズ初期化 を
使います。 これはモジュールが複数のインタープリタを正しくサポートする
ことを示します。

"PyModuleDef.m_size" に正の数を設定すると、そのバイト数のモジュール専
用の記憶領域を確保できます。 通常は、モジュールの C レベルの状態をすべ
て格納する、モジュール固有の "struct" のサイズを指定します。 特に、C
コードの動作に必要なクラスへのポインタ (例外を含み、静的な型を除きます
) や、"csv" の "field_size_limit" などの設定は、ここに格納してください
。

注釈:

  状態をモジュールの "__dict__" に格納する方法もありますが、ユーザーが
  Python コードで "__dict__" を変更してもクラッシュしないようにする必
  要があります。 通常は C レベルでエラーや型を検査することになりますが
  、これは間違えやすく、十分なテストも困難です。ただし、C コードでモジ
  ュールの状態を使わないなら、"__dict__" だけに格納するのがよい方法で
  す。

モジュールの状態に "PyObject" ポインタが含まれる場合、モジュールオブジ
ェクトはそのオブジェクトへの参照を保持し、モジュールレベルのフック
"m_traverse"、"m_clear"、"m_free" を実装する必要があります。 これらは
クラスの "tp_traverse"、"tp_clear"、"tp_free" のように働きます。 フッ
クの追加には手間がかかり、コードも長くなります。 これはモジュールをき
れいに解放できるようにするための代償です。

モジュールごとの状態を使うモジュールの例として、xxlimited があります。
モジュールの初期化の例はファイルの末尾にあります。


対応しない場合: プロセスごとに一つのモジュールオブジェクトに制限する
--------------------------------------------------------------------

"PyModuleDef.m_size" が非負であることは、モジュールが複数のインタープ
リタを正しくサポートすることを示します。 まだ対応していない場合は、プ
ロセスごとに一度だけ読み込めるよう明示的に制限できます。 例えば次のよ
うにします。

   // プロセス全体で共有するフラグ
   static int loaded = 0;

   // スレッド安全性を確保するミューテックス (フリースレッド版 Python でのみ必要)
   static PyMutex modinit_mutex = {0};

   static int
   exec_module(PyObject* module)
   {
       PyMutex_Lock(&modinit_mutex);
       if (loaded) {
           PyMutex_Unlock(&modinit_mutex);
           PyErr_SetString(PyExc_ImportError,
                           "cannot load module more than once per process");
           return -1;
       }
       loaded = 1;
       PyMutex_Unlock(&modinit_mutex);
       // ... 残りの初期化処理
   }

モジュールの "PyModuleDef.m_clear" 関数で将来の再初期化に備えられる場
合は、そこで "loaded" フラグをクリアしてください。 この場合、複数のイ
ンスタンスの *同時* 存在はサポートしませんが、例えば Python ランタイム
を終了 ("Py_FinalizeEx()") してから再初期化 ("Py_Initialize()") した後
で、モジュールを読み込むことはできます。


関数からモジュールの状態にアクセスする
--------------------------------------

モジュールレベルの関数から状態にアクセスするのは簡単です。 関数の最初
の引数にはモジュールオブジェクトが渡されます。 "PyModule_GetState" を
使うと状態を取得できます。

   static PyObject *
   func(PyObject *module, PyObject *args)
   {
       my_struct *state = (my_struct*)PyModule_GetState(module);
       if (state == NULL) {
           return NULL;
       }
       // ... 残りの処理
   }

注釈:

  モジュールの状態がない場合、つまり "PyModuleDef.m_size" がゼロの場合
  、"PyModule_GetState" は例外を設定せずに "NULL" を返すことがあります
  。 自分のモジュールでは "m_size" を管理できるので、これは簡単に防げ
  ます。


ヒープ型
========

従来、C コードで定義する型は *静的* なものです。 つまり、コード内に
"static PyTypeObject" 構造体を直接定義し、"PyType_Ready()" で初期化し
ます。

このような型は必然的にプロセス全体で共有されます。 モジュールオブジェ
クト間で共有するには、その型が所有したりアクセスしたりする状態に注意が
必要です。 問題を抑えるため、静的な型は Python レベルでは不変になって
います。 例えば "str.myattribute = 123" は設定できません。

**CPython 実装の詳細:** Sharing truly immutable objects between
interpreters is fine, as long as they don't provide access to mutable
objects. However, in CPython, every Python object has a mutable
implementation detail: the reference count. Changes to the refcount
are guarded by the GIL. Thus, code that shares any Python objects
across interpreters implicitly depends on CPython's current, process-
wide GIL.

静的な型は不変で、プロセス全体に属するため、「自分の」モジュールの状態
にアクセスできません。 その型のメソッドがモジュールの状態にアクセスす
る必要がある場合、*ヒープに割り当てる型*、略して *ヒープ型* に変換しな
ければなりません。 ヒープ型は Python の "class" 文で作成するクラスによ
り近いものです。

新しいモジュールでは、基本的にヒープ型を使うのがよい方針です。


静的な型をヒープ型に変更する
----------------------------

静的な型はヒープ型に変換できます。 ただし、ヒープ型の API は「完全に同
じ動作を保つ」変換、つまり元の静的な型とまったく同じ動作をする型の作成
を目的に設計されたものではありません。 新しい API でクラス定義を書き直
すと、pickle 化の可否や継承するスロットなど、一部の詳細が意図せず変わ
ることがあります。 重要な点は必ずテストしてください。

特に次の二点に注意してください (ただし、これですべてではありません):

* 静的な型とは異なり、ヒープ型のオブジェクトはデフォルトで可変です。
  変更を禁止するには "Py_TPFLAGS_IMMUTABLETYPE" フラグを使ってください
  。

* ヒープ型はデフォルトで "tp_new" を継承するため、Python コードからイ
  ンスタンスを作成できるようになる場合があります。
  "Py_TPFLAGS_DISALLOW_INSTANTIATION" フラグでこれを防げます。


ヒープ型の定義
--------------

ヒープ型は、クラスの説明や「設計図」に当たる "PyType_Spec" 構造体に値
を設定し、"PyType_FromModuleAndSpec()" を呼び出して新しいクラスオブジ
ェクトを構築することで作成できます。

注釈:

  "PyType_FromSpec()" などの他の関数でもヒープ型を作成できますが、
  "PyType_FromModuleAndSpec()" はクラスにモジュールを関連付けるため、
  メソッドからモジュールの状態にアクセスできます。

一般にクラスは、C から安全にアクセスするためのモジュールの状態と、
Python コードからアクセスするためのモジュールの "__dict__" の *両方*
に格納してください。


ガベージコレクションのプロトコル
--------------------------------

ヒープ型のインスタンスは自分の型への参照を保持します。 これにより、す
べてのインスタンスが破棄されるまで型は破棄されません。 ただし、ガベー
ジコレクタで解消する必要のある循環参照が生じる場合があります。

メモリリークを防ぐには、ヒープ型のインスタンスでガベージコレクションの
プロトコルを実装する必要があります。 具体的には、ヒープ型で次を行いま
す:

* "Py_TPFLAGS_HAVE_GC" フラグを設定する。

* "Py_tp_traverse" で走査関数を定義し、例えば
  "Py_VISIT(Py_TYPE(self))" を使って型を訪問する。

その他の注意点は "Py_TPFLAGS_HAVE_GC" と "tp_traverse" のドキュメント
を参照してください。

ヒープ型を定義する API は必要に応じて拡張されてきたため、現在の形には
多少使いづらいところがあります。 以降の節では、よくある問題を説明しま
す。


Python 3.8 以前の "tp_traverse"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

"tp_traverse" から型を訪問するという要件は Python 3.9 で追加されました
。 Python 3.8 以前をサポートする場合、走査関数は型を訪問しては *ならな
い* ため、少し複雑になります。

   static int my_traverse(PyObject *self, visitproc visit, void *arg)
   {
       if (Py_Version >= 0x03090000) {
           Py_VISIT(Py_TYPE(self));
       }
       return 0;
   }

残念ながら "Py_Version" が追加されたのは Python 3.11 です。 代わりに次
を使ってください:

* Stable ABI を使わない場合は "PY_VERSION_HEX"。

* それ以外は "sys.version_info" ("PySys_GetObject()" と
  "PyArg_ParseTuple()" を介して取得)。


"tp_traverse" の委譲
~~~~~~~~~~~~~~~~~~~~

走査関数が基底クラス (または他の型) の "tp_traverse" に処理を委譲する
場合、"Py_TYPE(self)" を訪問するのが一度だけになるようにしてください。
"tp_traverse" で型を訪問するのはヒープ型だけです。

例えば、走査関数に次が含まれ、

   base->tp_traverse(self, visit, arg)

"base" が静的な型の場合もあるなら、次も追加してください。

   if (base->tp_flags & Py_TPFLAGS_HEAPTYPE) {
       // ヒープ型の tp_traverse はすでに Py_TYPE(self) を訪問している
   } else {
       if (Py_Version >= 0x03090000) {
           Py_VISIT(Py_TYPE(self));
       }
   }

"tp_new" と "tp_clear" で型の参照カウントを操作する必要はありません。


"tp_dealloc" の定義
~~~~~~~~~~~~~~~~~~~

型に独自の "tp_dealloc" 関数がある場合、次の処理が必要です:

* いずれかのフィールドを無効にする前に "PyObject_GC_UnTrack()" を呼び
  出す。

* 型の参照カウントを減らす。

"tp_free" の呼び出し中も型を有効に保つため、型の参照カウントはインスタ
ンスを解放した *後* に減らす必要があります。 例えば次のようにします。

   static void my_dealloc(PyObject *self)
   {
       PyObject_GC_UnTrack(self);
       ...
       PyTypeObject *type = Py_TYPE(self);
       type->tp_free(self);
       Py_DECREF(type);
   }

デフォルトの "tp_dealloc" 関数はこの処理を行うため、"tp_dealloc" をオ
ーバーライド *しない* 型では追加する必要はありません。


"tp_free" をオーバーライドしない
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

ヒープ型の "tp_free" スロットは "PyObject_GC_Del()" に設定する必要があ
ります。 これがデフォルトなので、オーバーライドしないでください。


"PyObject_New" を避ける
~~~~~~~~~~~~~~~~~~~~~~~

GC が追跡するオブジェクトは、GC に対応した関数で割り当てる必要がありま
す。

"PyObject_New()" や "PyObject_NewVar()" を使っている場合:

* 可能なら、型の "tp_alloc" スロットを取得して呼び出してください。 つ
  まり "TYPE *o = PyObject_New(TYPE, typeobj)" を次に置き換えます。

     TYPE *o = typeobj->tp_alloc(typeobj, 0);

  "o = PyObject_NewVar(TYPE, typeobj, size)" も同様に置き換えますが、0
  の代わりに "size" を使います。

* それができない場合 (例えば独自の "tp_alloc" の内部) は、
  "PyObject_GC_New()" または "PyObject_GC_NewVar()" を呼び出してくださ
  い。

     TYPE *o = PyObject_GC_New(TYPE, typeobj);

     TYPE *o = PyObject_GC_NewVar(TYPE, typeobj, size);


クラスからモジュールの状態にアクセスする
----------------------------------------

"PyType_FromModuleAndSpec()" で定義した型オブジェクトがあれば、
"PyType_GetModule()" で関連付けられたモジュールを取得し、
"PyModule_GetState()" でその状態を取得できます。

エラー処理の定型コードを省くには、"PyType_GetModuleState()" でこの二つ
の手順をまとめられます。

   my_struct *state = (my_struct*)PyType_GetModuleState(type);
   if (state == NULL) {
       return NULL;
   }


通常のメソッドからモジュールの状態にアクセスする
------------------------------------------------

クラスのメソッドからモジュールレベルの状態にアクセスするのは少し複雑で
すが、Python 3.9 で導入された API により可能になりました。 まず *メソ
ッドを定義したクラス* を取得し、そのクラスからモジュールの状態を取得し
ます。

最大の難所は、*メソッドが定義されたクラス*、略して「定義クラス」を取得
することです。 定義クラスは、そのクラスが属するモジュールへの参照を保
持できます。

定義クラスと "Py_TYPE(self)" を混同しないでください。 型の *サブクラス
* のインスタンスでメソッドが呼び出された場合、"Py_TYPE(self)" はそのサ
ブクラスを指します。 そのサブクラスは別のモジュールで定義されているか
もしれません。

注釈:

  次の Python コードでこの考え方を示します。 "Base.get_defining_class"
  は "type(self) == Sub" の場合でも "Base" を返します:

     class Base:
         def get_type_of_self(self):
             return type(self)

         def get_defining_class(self):
             return __class__

     class Sub(Base):
         pass

メソッドが「定義クラス」を取得するには、METH_METHOD | METH_FASTCALL |
METH_KEYWORDS "呼び出し規約" と、それに対応する "PyCMethod" シグネチャ
を使う必要があります。

   PyObject *PyCMethod(
       PyObject *self,               // object the method was called on
       PyTypeObject *defining_class, // defining class
       PyObject *const *args,        // C array of arguments
       Py_ssize_t nargs,             // length of "args"
       PyObject *kwnames)            // NULL, or dict of keyword arguments

定義クラスを取得したら、"PyType_GetModuleState()" を呼び出して、関連付
けられたモジュールの状態を取得します。

例えば:

   static PyObject *
   example_method(PyObject *self,
           PyTypeObject *defining_class,
           PyObject *const *args,
           Py_ssize_t nargs,
           PyObject *kwnames)
   {
       my_struct *state = (my_struct*)PyType_GetModuleState(defining_class);
       if (state == NULL) {
           return NULL;
       }
       ... // rest of logic
   }

   PyDoc_STRVAR(example_method_doc, "...");

   static PyMethodDef my_methods[] = {
       {"example_method",
         (PyCFunction)(void(*)(void))example_method,
         METH_METHOD|METH_FASTCALL|METH_KEYWORDS,
         example_method_doc}
       {NULL},
   }


スロットメソッド、ゲッター、セッターからモジュールの状態にアクセスする
----------------------------------------------------------------------

注釈:

  これは Python 3.11 の新機能です。

スロットメソッドは、特殊メソッドに相当する高速な C の処理です。 例えば
"__add__" に対応する "nb_add" や、初期化に使う "tp_new" があります。
これらの API は非常に単純で、"PyCMethod" とは異なり定義クラスを渡せま
せん。 "PyGetSetDef" で定義するゲッターやセッターも同様です。

このような場合にモジュールの状態にアクセスするには、
"PyType_GetModuleByDef()" 関数にモジュール定義を渡します。 モジュール
を取得したら、"PyModule_GetState()" を呼び出して状態を取得します。

   PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &module_def);
   my_struct *state = (my_struct*)PyModule_GetState(module);
   if (state == NULL) {
       return NULL;
   }

"PyType_GetModuleByDef()" は *method resolution order* (すべてのスーパ
ークラス) を検索し、対応するモジュールを持つ最初のスーパークラスを探し
ます。

注釈:

  非常に特殊な場合 (同じ定義から作成された複数のモジュールに継承関係が
  またがる場合)、"PyType_GetModuleByDef()" は本来の定義クラスのモジュ
  ールを返さないことがあります。 ただし、必ず同じ定義のモジュールを返
  すため、C のメモリレイアウトの互換性は保証されます。


モジュールの状態の寿命
----------------------

モジュールオブジェクトがガベージコレクションで回収されると、モジュール
の状態も解放されます。 モジュールの状態 (またはその一部) へのポインタ
を保持する間は、それぞれモジュールオブジェクトへの参照を保持しなければ
なりません。

"PyType_FromModuleAndSpec()" で作成した型とそのインスタンスはモジュー
ルへの参照を保持するため、通常これは問題になりません。 ただし、外部ラ
イブラリのコールバックなど、他の場所からモジュールの状態を参照する場合
は、参照カウントに注意する必要があります。


取り掛かり中の問題
==================

モジュールごとの状態とヒープ型には、まだ未解決の問題がいくつかあります
。

改善について議論するには、discuss フォーラムの c-api カテゴリ が適して
います。


クラスごとのスコープ
--------------------

It is currently (as of Python 3.11) not possible to attach state to
individual *types* without relying on CPython implementation details
(which may change in the future—perhaps, ironically, to allow a proper
solution for per-class scope).


動作を完全に保ったヒープ型への変換
----------------------------------

ヒープ型の API は、静的な型から動作を完全に保ったまま変換すること、つ
まり元の静的な型とまったく同じ動作をする型の作成を目的に設計されたもの
ではありません。
