拡張モジュールを分離する¶
対象読者¶
このガイドは、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_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 は、静的な型から動作を完全に保ったまま変換すること、つまり元の静的な型とまったく同じ動作をする型の作成を目的に設計されたものではありません。