拡張の型の定義: チュートリアル¶
Python では、組み込みの str 型や list 型のような Python コードから操作できる新しい型を C 拡張モジュールの作者が定義できます。
全ての拡張の型のコードはあるパターンに従うのですが、書き始める前に理解しておくべき細かいことがあります。
このドキュメントはその話題についてのやさしい入門です。
基本的なこと¶
CPython ランタイムは、すべての Python オブジェクトを PyObject* 型の変数として扱います。
この型はすべての Python オブジェクトの「基底型」の役割を果たします。
PyObject 構造体自体に含まれるのは、オブジェクトの 参照カウント と、その「型オブジェクト」へのポインタだけです。
動作を決めるのは、この型オブジェクトです。
例えばオブジェクトの属性を検索するとき、メソッドを呼び出すとき、ほかのオブジェクトとの乗算を行うときに、インタープリタがどの C 関数を呼び出すかを決定します。
これらの C 関数を「型メソッド」と呼びます。
それなので、新しい拡張の型を定義したいときは、新しい型オブジェクトを作成すればよいわけです。
この手のことは例を見たほうが早いでしょうから、以下に C 拡張モジュール custom にある Custom という名前の新しい型を定義する、最小限ながら完全なモジュールをあげておきます:
注釈
ここで紹介している例は、 静的な 拡張の型を定義する伝統的な実装方法です。
これはほとんどの場面で十分なものなのです。
C API では、 PyType_FromSpec() 関数を使い、ヒープ上に配置された拡張の型も定義できますが、これについてはこのチュートリアルでは扱いません。
#define PY_SSIZE_T_CLEAN
#include <Python.h>
typedef struct {
PyObject_HEAD
/* 型固有のフィールドをここに記述します。 */
} CustomObject;
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom.Custom",
.tp_doc = PyDoc_STR("Custom オブジェクト"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
};
static int
custom_module_exec(PyObject *m)
{
if (PyType_Ready(&CustomType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot custom_module_slots[] = {
{Py_mod_exec, custom_module_exec},
// 静的な型を使う間はこの設定を使う
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{0, NULL}
};
static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom",
.m_doc = "拡張型を作成するサンプルモジュール。",
.m_size = 0,
.m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom(void)
{
return PyModuleDef_Init(&custom_module);
}
一度に理解するにはかなりの量ですが、前の章で見たことのある部分もあるでしょう。 このファイルでは、次の三つを定義しています。
Customオブジェクト が何を含んでいるか: これがCustomObject構造体で、Customインスタンスごとに1回だけメモリ確保が行われます。Custom型 がどのように振る舞うか: これがCustomType構造体で、フラグと関数ポインタの集まりを定義しています。特定の操作が要求されたときに、この関数ポインタをインタープリターが見に行きます。customモジュールをどう定義し、実行するか。 モジュールを定義するPyInit_custom関数と、それに関連するcustom_module構造体、および新しいモジュールオブジェクトを設定するcustom_module_exec関数がこれに当たります。
まず最初はこれです:
typedef struct {
PyObject_HEAD
} CustomObject;
これが Custom オブジェクトの内容です。
各オブジェクト構造体の先頭には PyObject_HEAD が必須で、これは PyObject 型の ob_base というフィールドを定義します。
このフィールドには型オブジェクトへのポインタと参照カウントが含まれ、それぞれ Py_TYPE と Py_REFCNT マクロでアクセスできます。
このマクロはレイアウトを抽象化し、デバッグビルド でフィールドを追加できるようにするためのものです。
注釈
上の例では PyObject_HEAD マクロの後にセミコロンはありません。
うっかりセミコロンを追加しないように気を付けてください: これを警告するコンパイラもあります。
もちろん、一般的にはオブジェクトは標準的な PyObject_HEAD ボイラープレートの他にもデータを保持しています; 例えば、これは Python 標準の浮動小数点数の定義です:
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
2つ目は型オブジェクトの定義です。
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom.Custom",
.tp_doc = PyDoc_STR("Custom オブジェクト"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew,
};
注釈
上にあるように C99 スタイルの指示付き初期化子を使って、 PyTypeObject の特に関心の無いフィールドまで全て並べたり、フィールドを宣言する順序に気を使ったりせずに済ませるのをお薦めします。
object.h にある実際の PyTypeObject の定義には上の定義にあるよりももっと多くの フィールド があります。
ここに出てきていないフィールドは C コンパイラによってゼロで埋められるので、必要でない限り明示的には値の指定をしないのが一般的な作法になっています。
一度に1つずつフィールドを取り上げていきましょう:
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
この行は、上で説明した ob_base フィールドを初期化するために必要な定型的な記述です。
.tp_name = "custom.Custom",
実装している型の名前です。 これは、オブジェクトのデフォルトの文字列表現やエラーメッセージに現れます。例えば次の通りです:
>>> "" + custom.Custom()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "custom.Custom") to str
型の名前は、モジュール名とモジュール内の型の名前をドットでつないだ名前です。
この場合、モジュールは custom、型は Custom なので、型の名前を custom.Custom に設定します。
この型を pydoc や pickle モジュールと互換性のあるものにするには、実際のドット付きインポートパスを使うことが重要です。
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
これは、新しい Custom インスタンスを作るときに Python が割り当てるべきメモリがどのくらいなのかを知るためのものです。 tp_itemsize は可変サイズのオブジェクトでのみ使うものなので、サイズが可変でないオブジェクトでは 0 にすべきです。
注釈
Python からサブクラスを作成できる型で、その tp_basicsize が基底型と同じ場合は、多重継承で問題が起こる可能性があります。
その型の Python サブクラスでは、__bases__ の先頭にその型を指定しないと、その型の __new__() メソッドを呼び出す際にエラーが発生します。
この問題は、型の tp_basicsize を基底型より大きな値にすることで避けられます。
たいていの場合、基底型が object であるか、基底型にデータメンバを追加してサイズを増やすので、すでにこの条件を満たしています。
Py_TPFLAGS_DEFAULT にクラスフラグを設定します。
.tp_flags = Py_TPFLAGS_DEFAULT,
すべての型はフラグにこの定数を含めておく必要があります。これは最低でも Python 3.3 までに定義されているすべてのメンバを許可します。それ以上のメンバが必要なら、対応するフラグの OR をとる必要があります。
この型の docstring は tp_doc に入れます。
.tp_doc = PyDoc_STR("Custom オブジェクト"),
オブジェクトが生成できるように、 tp_new ハンドラを提供する必要があります。これは Python のメソッド __new__() と同等のものですが、明示的に与える必要があります。今の場合では、 API 関数の PyType_GenericNew() として提供されるデフォルトの実装をそのまま使えます。
.tp_new = PyType_GenericNew,
ファイル内の残りの部分は、custom_module_exec() の一部を除いて、すでに見慣れたものでしょう。
if (PyType_Ready(&CustomType) < 0) {
return -1;
}
これは、 NULL に初期化された ob_type も含めて、いくつかのメンバーを適切なデフォルト値で埋めて、 Custom 型を初期化します。
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
return -1;
}
これは型をモジュールの辞書に追加します。こうすることで Custom クラスの呼び出しで Custom インスタンスが作成できるようになります:
>>> import custom
>>> mycustom = custom.Custom()
以上です!残りの作業はビルドだけです; custom.c という名前のファイルにここまでのコードを書き込み、
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "custom"
version = "1"
pyproject.toml というファイルにこれを書き込み、
from setuptools import Extension, setup
setup(ext_modules=[Extension("custom", ["custom.c"])])
そして setup.py というファイルにこのように書き込み、そして、以下のように
$ python -m pip install .
シェルに入力します。これにより、サブディレクトリに custom.so が生成され、インストールされます。これで、 Python を立ち上げて、 import custom すると Custom オブジェクトで遊べるようになっているはずです。
そんなにむずかしくありません、よね?
もちろん、現在の Custom 型は面白みに欠けています。何もデータを持っていないし、何もできません。継承してサブクラスを作ることさえできないのです。
基本のサンプルにデータとメソッドを追加する¶
この基本のサンプルにデータとメソッドを追加してみましょう。ついでに、この型を基底クラスとしても利用できるようにします。ここでは新しいモジュール custom2 をつくり、これらの機能を追加します:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stddef.h> /* offsetof() 用 */
typedef struct {
PyObject_HEAD
PyObject *first; /* ファーストネーム */
PyObject *last; /* ラストネーム */
int number;
} CustomObject;
static void
Custom_dealloc(PyObject *op)
{
CustomObject *self = (CustomObject *) op;
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free(self);
}
static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
CustomObject *self;
self = (CustomObject *) type->tp_alloc(type, 0);
if (self != NULL) {
self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->first == NULL) {
Py_DECREF(self);
return NULL;
}
self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->last == NULL) {
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *) self;
}
static int
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
CustomObject *self = (CustomObject *) op;
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
Py_XSETREF(self->first, Py_NewRef(first));
}
if (last) {
Py_XSETREF(self->last, Py_NewRef(last));
}
return 0;
}
static PyMemberDef Custom_members[] = {
{"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
"ファーストネーム"},
{"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
"ラストネーム"},
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"独自の数値"},
{NULL} /* 番兵 */
};
static PyObject *
Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
{
CustomObject *self = (CustomObject *) op;
if (self->first == NULL) {
PyErr_SetString(PyExc_AttributeError, "first");
return NULL;
}
if (self->last == NULL) {
PyErr_SetString(PyExc_AttributeError, "last");
return NULL;
}
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
static PyMethodDef Custom_methods[] = {
{"name", Custom_name, METH_NOARGS,
"ファーストネームとラストネームを連結した名前を返す"
},
{NULL} /* 番兵 */
};
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom2.Custom",
.tp_doc = PyDoc_STR("Custom オブジェクト"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = Custom_new,
.tp_init = Custom_init,
.tp_dealloc = Custom_dealloc,
.tp_members = Custom_members,
.tp_methods = Custom_methods,
};
static int
custom_module_exec(PyObject *m)
{
if (PyType_Ready(&CustomType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot custom_module_slots[] = {
{Py_mod_exec, custom_module_exec},
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{0, NULL}
};
static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom2",
.m_doc = "拡張型を作成するサンプルモジュール。",
.m_size = 0,
.m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom2(void)
{
return PyModuleDef_Init(&custom_module);
}
モジュールのこのバージョンでは、いくつもの変更をおこないます。
Custom 型は そのC構造体に 3つのデータ属性 first 、 last 、および number をもつようになりました。 first と last 属性はファーストネームとラストネームを格納した Python 文字列で、 number 属性は C言語での整数の値です。
これにしたがうと、オブジェクトの構造体は次のようになります:
typedef struct {
PyObject_HEAD
PyObject *first; /* ファーストネーム */
PyObject *last; /* ラストネーム */
int number;
} CustomObject;
いまや管理すべきデータができたので、オブジェクトの割り当てと解放に際してはより慎重になる必要があります。最低限、オブジェクトの解放メソッドが必要です:
static void
Custom_dealloc(PyObject *op)
{
CustomObject *self = (CustomObject *) op;
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free(self);
}
この関数は tp_dealloc メンバに代入されます。
.tp_dealloc = Custom_dealloc,
このメソッドは、まず二つのPython 属性の参照カウントをクリアします。 Py_XDECREF() は引数が NULL のケースを正しく扱えます (これは、tp_new が途中で失敗した場合に起こりえます) 。このメソッドは、つぎにオブジェクトの型 (Py_TYPE(self) で算出します) のメンバ tp_free を呼び出し、オブジェクトのメモリを開放します。オブジェクトの型が CustomType であるとは限らない点に注意してください。なぜなら、オブジェクトはサブクラスのインスタンスかもしれないからです。
注釈
tp_dealloc 関数ポインタが PyObject * 引数を受け取るため、Custom_dealloc も PyObject * 引数を受け取るように定義しています。
そのため、上の例では CustomObject * への明示的なキャストが必要です。
この関数を型の tp_dealloc スロットに代入することで、CustomObject クラスのインスタンスに対してだけ呼び出されることを宣言しているので、(CustomObject *) へのキャストは安全です。
これが C でのオブジェクト指向のポリモーフィズムです。
既存のコードやこのチュートリアルの以前のバージョンでは、次のようにサブタイプのオブジェクト構造体へのポインタ (CustomObject*) を直接受け取る関数を見かけるかもしれません。
Custom_dealloc(CustomObject *self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject *) self);
}
...
.tp_dealloc = (destructor) Custom_dealloc,
CPython がサポートするすべてのアーキテクチャでは同じ動作になりますが、C の規格上は未定義の動作を引き起こします。
ファーストネームとラストネームを空文字列に初期化しておきたいので、tp_new の実装を追加することにしましょう:
static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
CustomObject *self;
self = (CustomObject *) type->tp_alloc(type, 0);
if (self != NULL) {
self->first = PyUnicode_FromString("");
if (self->first == NULL) {
Py_DECREF(self);
return NULL;
}
self->last = PyUnicode_FromString("");
if (self->last == NULL) {
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *) self;
}
そしてこれを tp_new メンバとしてインストールします:
.tp_new = Custom_new,
tp_new ハンドラは、その型のオブジェクトの初期化ではなく、作成を担当します。
Python では __new__() メソッドとして公開されます。
tp_new メンバを独自に定義する必要はなく、実際、多くの拡張型は、上の最初の Custom 型と同じく PyType_GenericNew() をそのまま使います。
今回は tp_new ハンドラを使って、first と last 属性を NULL でないデフォルト値に初期化します。
tp_new にはインスタンス化する型 (サブクラスをインスタンス化する場合は必ずしも CustomType とは限りません) と、その型の呼び出し時に渡された引数を渡します。
作成したインスタンスを返す必要があります。
tp_new ハンドラは常に位置引数とキーワード引数を受け取りますが、それらを無視して、初期化メソッド (C では tp_init、Python では __init__) に引数の処理を任せることもよくあります。
注釈
tp_new は明示的に tp_init を呼び出してはいけません、これはインタープリタが自分で行うためです。
この tp_new の実装は、tp_alloc スロットを呼び出してメモリを割り当てます:
self = (CustomObject *) type->tp_alloc(type, 0);
メモリ割り当ては失敗するかもしれないので、先に進む前に tp_alloc の結果が NULL でないかチェックしなければなりません。
注釈
ここでは tp_alloc スロットを自分では設定していません。
PyType_Ready() が、デフォルトでは object となる基底クラスから継承して設定してくれます。
ほとんどの型は、デフォルトの割り当て方法を使います。
注釈
基底型の tp_new や __new__() を呼び出す、協調的な tp_new を実装する場合、実行時のメソッド解決順序を使って呼び出すメソッドを決めようとしては いけません。
呼び出す型を常に静的に決め、その tp_new を直接、または type->tp_base->tp_new を通して呼び出してください。
そうしないと、この型の Python サブクラスが、ほかの Python で定義されたクラスも継承する場合に正しく動作しない可能性があります。
具体的には、そのようなサブクラスのインスタンスを作ろうとすると TypeError が発生する場合があります。
インスタンスの初期値を引数として受け取る初期化関数も定義し、
static int
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
CustomObject *self = (CustomObject *) op;
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL, *tmp;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
tmp = self->first;
Py_INCREF(first);
self->first = first;
Py_XDECREF(tmp);
}
if (last) {
tmp = self->last;
Py_INCREF(last);
self->last = last;
Py_XDECREF(tmp);
}
return 0;
}
これは tp_init メンバに代入されます。
.tp_init = Custom_init,
tp_init スロットは、Python では __init__() メソッドとして公開されます。
作成後のオブジェクトを初期化するために使います。
初期化関数は常に位置引数とキーワード引数を受け取り、成功した場合は 0、エラーの場合は -1 を返す必要があります。
tp_new ハンドラと異なり、tp_init が呼ばれる保証はありません (例えば pickle モジュールは、デフォルトでは unpickle したインスタンスに対して __init__() を呼び出しません)。
また、複数回呼ばれることもあります。
誰でもこのオブジェクトの __init__() メソッドを呼び出せるからです。
そのため、新しい属性値を代入するときは特に注意する必要があります。
例えば、first メンバを次のように代入したくなるかもしれません。
if (first) {
Py_XDECREF(self->first);
Py_INCREF(first);
self->first = first;
}
しかし、これは危険です。
この型は first メンバの型を制限していないので、どのようなオブジェクトでも格納できます。
そのオブジェクトのデストラクタが、first メンバにアクセスするコードを実行する可能性があります。
また、デストラクタが スレッド状態 をデタッチし、別のスレッドで任意のコードが動いて、このオブジェクトにアクセスしたり変更したりする可能性もあります。
この可能性にも十分に注意して対処するため、ほとんどの場合は、参照カウントを減らす前にメンバに新しい値を代入します。 では、このようにする必要がないのはどのような場合でしょうか。
その参照カウントが 1 より大きいと確信できる場合
オブジェクト [1] の解放で、スレッド状態 がデタッチされることも、自分の型のコードが呼び戻されることもないと分かっている場合。
循環ガベージコレクションをサポートしない型の
tp_deallocハンドラ内で、参照カウントを減らす場合 [2]。
ここではインスタンス変数を属性として見えるようにしたいのですが、これにはいくつもの方法があります。もっとも簡単な方法は、メンバの定義を与えることです:
static PyMemberDef Custom_members[] = {
{"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
"ファーストネーム"},
{"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
"ラストネーム"},
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"独自の数値"},
{NULL} /* 番兵 */
};
そして、この定義を tp_members スロットに入れましょう:
.tp_members = Custom_members,
各メンバ定義には、メンバ名、型、オフセット、アクセスフラグ、ドキュメント文字列があります。 詳細は後述の 総称的な属性を管理する 節を参照してください。
この方法の欠点は、Python の属性に代入できるオブジェクトの型を制限できないことです。
ファーストネームとラストネームには文字列を想定していますが、任意の Python オブジェクトを代入できてしまいます。
さらに、属性を削除すると、C のポインタが NULL になります。
メンバを NULL でない値に初期化しても、属性が削除されれば NULL に設定されます。
ファーストネームとラストネームを連結してオブジェクトの名前を返す、Custom.name() というメソッドを一つ定義します。
static PyObject *
Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
{
CustomObject *self = (CustomObject *) op;
if (self->first == NULL) {
PyErr_SetString(PyExc_AttributeError, "first");
return NULL;
}
if (self->last == NULL) {
PyErr_SetString(PyExc_AttributeError, "last");
return NULL;
}
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
このメソッドは、Custom (または Custom のサブクラス) のインスタンスを第 1 引数として受け取る C 関数として実装します。
メソッドは常に第 1 引数としてインスタンスを受け取ります。
多くの場合は位置引数やキーワード引数も受け取りますが、ここではどちらも受け取らないため、位置引数のタプルやキーワード引数の辞書を受け取る必要はありません。
このメソッドは、次の Python メソッドに相当します。
def name(self):
return "%s %s" % (self.first, self.last)
first と last メンバが NULL である可能性を確認する必要があります。
これらの属性は削除可能で、削除された場合は NULL に設定されるためです。
これらの属性を削除できないようにし、値を文字列に制限するとよいでしょう。
次の節でその方法を説明します。
さて、メソッドを定義したので、ここでメソッド定義用の配列を作成する必要があります:
static PyMethodDef Custom_methods[] = {
{"name", Custom_name, METH_NOARGS,
"ファーストネームとラストネームを連結した名前を返す"
},
{NULL} /* 番兵 */
};
(self 以外の引数を受け取らないことを示すために、METH_NOARGS フラグを使っています。)
そして、それを tp_methods スロットに代入します。
.tp_methods = Custom_methods,
最後に、この型をサブクラスの基底クラスとして使えるようにします。
ここまで、作成するオブジェクトや操作対象のオブジェクトの型を決めつけないように注意してメソッドを書いてきました。
そのため、クラスのフラグ定義に Py_TPFLAGS_BASETYPE を追加するだけで済みます。
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
PyInit_custom() を PyInit_custom2() に改名し、PyModuleDef 構造体のモジュール名と PyTypeObject 構造体の完全なクラス名を更新します。
最後に、新しいモジュールを含めるよう setup.py ファイルを更新し、
from setuptools import Extension, setup
setup(ext_modules=[
Extension("custom", ["custom.c"]),
Extension("custom2", ["custom2.c"]),
])
import custom2 ができるように再インストールします。
$ python -m pip install .
データ属性をこまかく制御する¶
この節では、Custom の例で first と last 属性を設定する方法を、より細かく制御します。
前のバージョンのモジュールでは、インスタンス変数 first と last に文字列以外の値を設定でき、削除することさえできました。
これらの属性が常に文字列を格納するようにしたいと思います。
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stddef.h> /* offsetof() 用 */
typedef struct {
PyObject_HEAD
PyObject *first; /* ファーストネーム */
PyObject *last; /* ラストネーム */
int number;
} CustomObject;
static void
Custom_dealloc(PyObject *op)
{
CustomObject *self = (CustomObject *) op;
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free(self);
}
static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
CustomObject *self;
self = (CustomObject *) type->tp_alloc(type, 0);
if (self != NULL) {
self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->first == NULL) {
Py_DECREF(self);
return NULL;
}
self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->last == NULL) {
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *) self;
}
static int
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
CustomObject *self = (CustomObject *) op;
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
Py_SETREF(self->first, Py_NewRef(first));
}
if (last) {
Py_SETREF(self->last, Py_NewRef(last));
}
return 0;
}
static PyMemberDef Custom_members[] = {
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"独自の数値"},
{NULL} /* 番兵 */
};
static PyObject *
Custom_getfirst(PyObject *op, void *closure)
{
CustomObject *self = (CustomObject *) op;
return Py_NewRef(self->first);
}
static int
Custom_setfirst(PyObject *op, PyObject *value, void *closure)
{
CustomObject *self = (CustomObject *) op;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The first attribute value must be a string");
return -1;
}
Py_SETREF(self->first, Py_NewRef(value));
return 0;
}
static PyObject *
Custom_getlast(PyObject *op, void *closure)
{
CustomObject *self = (CustomObject *) op;
return Py_NewRef(self->last);
}
static int
Custom_setlast(PyObject *op, PyObject *value, void *closure)
{
CustomObject *self = (CustomObject *) op;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The last attribute value must be a string");
return -1;
}
Py_SETREF(self->last, Py_NewRef(value));
return 0;
}
static PyGetSetDef Custom_getsetters[] = {
{"first", Custom_getfirst, Custom_setfirst,
"ファーストネーム", NULL},
{"last", Custom_getlast, Custom_setlast,
"ラストネーム", NULL},
{NULL} /* 番兵 */
};
static PyObject *
Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
{
CustomObject *self = (CustomObject *) op;
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
static PyMethodDef Custom_methods[] = {
{"name", Custom_name, METH_NOARGS,
"ファーストネームとラストネームを連結した名前を返す"
},
{NULL} /* 番兵 */
};
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom3.Custom",
.tp_doc = PyDoc_STR("Custom オブジェクト"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = Custom_new,
.tp_init = Custom_init,
.tp_dealloc = Custom_dealloc,
.tp_members = Custom_members,
.tp_methods = Custom_methods,
.tp_getset = Custom_getsetters,
};
static int
custom_module_exec(PyObject *m)
{
if (PyType_Ready(&CustomType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot custom_module_slots[] = {
{Py_mod_exec, custom_module_exec},
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{0, NULL}
};
static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom3",
.m_doc = "拡張型を作成するサンプルモジュール。",
.m_size = 0,
.m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom3(void)
{
return PyModuleDef_Init(&custom_module);
}
first と last 属性をもっと細かく制御するため、独自の getter 関数と setter 関数を使います。
first 属性を取得、設定する関数は次のとおりです。
static PyObject *
Custom_getfirst(PyObject *op, void *closure)
{
CustomObject *self = (CustomObject *) op;
Py_INCREF(self->first);
return self->first;
}
static int
Custom_setfirst(PyObject *op, PyObject *value, void *closure)
{
CustomObject *self = (CustomObject *) op;
PyObject *tmp;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The first attribute value must be a string");
return -1;
}
tmp = self->first;
Py_INCREF(value);
self->first = value;
Py_DECREF(tmp);
return 0;
}
getter 関数には、Custom オブジェクトと、void ポインタである「クロージャ」を渡します。
ここではクロージャを無視します。
(クロージャは、定義に関するデータを getter や setter に渡す高度な使い方をサポートします。
例えば、クロージャ内のデータに応じて取得や設定を行う属性を決める、一組の getter 関数と setter 関数を用意できます。)
setter 関数には、Custom オブジェクト、新しい値、クロージャを渡します。
新しい値が NULL の場合は、属性の削除を意味します。
今回の setter では、属性を削除しようとした場合や、新しい値が文字列でない場合にエラーを送出します。
ここでは PyGetSetDef 構造体の配列をつくります:
static PyGetSetDef Custom_getsetters[] = {
{"first", Custom_getfirst, Custom_setfirst,
"ファーストネーム", NULL},
{"last", Custom_getlast, Custom_setlast,
"ラストネーム", NULL},
{NULL} /* 番兵 */
};
そしてこれを tp_getset スロットに登録します:
.tp_getset = Custom_getsetters,
PyGetSetDef 構造体の最後の項目は、先ほど説明した「クロージャ」です。
ここではクロージャを使わないので、単に NULL を渡します。
また、メンバ定義からはこれらの属性を除いておきましょう:
static PyMemberDef Custom_members[] = {
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"独自の数値"},
{NULL} /* 番兵 */
};
また、ここでは tp_init ハンドラも渡されるものとして文字列のみを許可するように修正する必要があります [3]:
static int
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
CustomObject *self = (CustomObject *) op;
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL, *tmp;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
tmp = self->first;
Py_INCREF(first);
self->first = first;
Py_DECREF(tmp);
}
if (last) {
tmp = self->last;
Py_INCREF(last);
self->last = last;
Py_DECREF(tmp);
}
return 0;
}
これらの変更により、first と last メンバが NULL にならないことを保証できるため、ほとんどの場合は NULL のチェックを削除できます。
つまり、Py_XDECREF() の呼び出しのほとんどを Py_DECREF() に置き換えられます。
唯一置き換えられないのは tp_dealloc の実装内です。
tp_new でこれらのメンバの初期化に失敗した可能性があるためです。
さて、先ほどもしたように、このモジュール初期化関数と初期化関数内にあるモジュール名を変更しましょう。そして setup.py ファイルに追加の定義をくわえます。
循環ガベージコレクションをサポートする¶
Python は 循環ガベージコレクタ (GC) 機能 をもっており、これは不要なオブジェクトを、たとえ参照カウントがゼロでなくても発見することができます。そのような状況はオブジェクトの参照が循環しているときに起こりえます。たとえば以下の例を考えてください:
>>> l = []
>>> l.append(l)
>>> del l
この例では、自分自身をふくむリストを作りました。たとえこのリストを 削除しても、それは自分自身への参照をまだ持ちつづけますから、参照カウントはゼロにはなりません。嬉しいことに Python には循環ガベージコレクタは最終的にはこのリストが不要であることを検出し、解放できます。
Custom の例の第 2 版では、first と last 属性にどのようなオブジェクトでも格納できるようにしました [4]。
さらに、第 2 版と第 3 版では Custom のサブクラス化を許可しました。
サブクラスには任意の属性を追加できます。
これらのどちらの理由によっても、Custom オブジェクトが参照の循環に加わる可能性があります。
>>> import custom3
>>> class Derived(custom3.Custom): pass
...
>>> n = Derived()
>>> n.some_attribute = n
参照の循環に加わった Custom インスタンスを循環 GC が正しく検出して回収できるようにするには、Custom 型でさらに二つのスロットを設定し、それらのスロットを有効にするフラグを設定する必要があります。
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stddef.h> /* offsetof() 用 */
typedef struct {
PyObject_HEAD
PyObject *first; /* ファーストネーム */
PyObject *last; /* ラストネーム */
int number;
} CustomObject;
static int
Custom_traverse(PyObject *op, visitproc visit, void *arg)
{
CustomObject *self = (CustomObject *) op;
Py_VISIT(self->first);
Py_VISIT(self->last);
return 0;
}
static int
Custom_clear(PyObject *op)
{
CustomObject *self = (CustomObject *) op;
Py_CLEAR(self->first);
Py_CLEAR(self->last);
return 0;
}
static void
Custom_dealloc(PyObject *op)
{
PyObject_GC_UnTrack(op);
(void)Custom_clear(op);
Py_TYPE(op)->tp_free(op);
}
static PyObject *
Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
CustomObject *self;
self = (CustomObject *) type->tp_alloc(type, 0);
if (self != NULL) {
self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->first == NULL) {
Py_DECREF(self);
return NULL;
}
self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
if (self->last == NULL) {
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *) self;
}
static int
Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
{
CustomObject *self = (CustomObject *) op;
static char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
&first, &last,
&self->number))
return -1;
if (first) {
Py_SETREF(self->first, Py_NewRef(first));
}
if (last) {
Py_SETREF(self->last, Py_NewRef(last));
}
return 0;
}
static PyMemberDef Custom_members[] = {
{"number", Py_T_INT, offsetof(CustomObject, number), 0,
"独自の数値"},
{NULL} /* 番兵 */
};
static PyObject *
Custom_getfirst(PyObject *op, void *closure)
{
CustomObject *self = (CustomObject *) op;
return Py_NewRef(self->first);
}
static int
Custom_setfirst(PyObject *op, PyObject *value, void *closure)
{
CustomObject *self = (CustomObject *) op;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The first attribute value must be a string");
return -1;
}
Py_XSETREF(self->first, Py_NewRef(value));
return 0;
}
static PyObject *
Custom_getlast(PyObject *op, void *closure)
{
CustomObject *self = (CustomObject *) op;
return Py_NewRef(self->last);
}
static int
Custom_setlast(PyObject *op, PyObject *value, void *closure)
{
CustomObject *self = (CustomObject *) op;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The last attribute value must be a string");
return -1;
}
Py_XSETREF(self->last, Py_NewRef(value));
return 0;
}
static PyGetSetDef Custom_getsetters[] = {
{"first", Custom_getfirst, Custom_setfirst,
"ファーストネーム", NULL},
{"last", Custom_getlast, Custom_setlast,
"ラストネーム", NULL},
{NULL} /* 番兵 */
};
static PyObject *
Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
{
CustomObject *self = (CustomObject *) op;
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
static PyMethodDef Custom_methods[] = {
{"name", Custom_name, METH_NOARGS,
"ファーストネームとラストネームを連結した名前を返す"
},
{NULL} /* 番兵 */
};
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom4.Custom",
.tp_doc = PyDoc_STR("Custom オブジェクト"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
.tp_new = Custom_new,
.tp_init = Custom_init,
.tp_dealloc = Custom_dealloc,
.tp_traverse = Custom_traverse,
.tp_clear = Custom_clear,
.tp_members = Custom_members,
.tp_methods = Custom_methods,
.tp_getset = Custom_getsetters,
};
static int
custom_module_exec(PyObject *m)
{
if (PyType_Ready(&CustomType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot custom_module_slots[] = {
{Py_mod_exec, custom_module_exec},
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{0, NULL}
};
static PyModuleDef custom_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom4",
.m_doc = "拡張型を作成するサンプルモジュール。",
.m_size = 0,
.m_slots = custom_module_slots,
};
PyMODINIT_FUNC
PyInit_custom4(void)
{
return PyModuleDef_Init(&custom_module);
}
まず、走査メソッドによって、参照の循環に加わる可能性のある子オブジェクトを循環 GC に知らせます。
static int
Custom_traverse(PyObject *op, visitproc visit, void *arg)
{
CustomObject *self = (CustomObject *) op;
int vret;
if (self->first) {
vret = visit(self->first, arg);
if (vret != 0)
return vret;
}
if (self->last) {
vret = visit(self->last, arg);
if (vret != 0)
return vret;
}
return 0;
}
参照の循環に加わる可能性のある子オブジェクトごとに、走査メソッドに渡された visit() 関数を呼び出す必要があります。
visit() 関数は、子オブジェクトと、走査メソッドに渡された追加の引数 arg を受け取ります。
戻り値は整数で、ゼロでなければその値をそのまま返す必要があります。
Python には、visit 関数の呼び出しを自動化する Py_VISIT() マクロがあります。
Py_VISIT() を使うと、Custom_traverse の定型的なコードを最小限にできます。
static int
Custom_traverse(PyObject *op, visitproc visit, void *arg)
{
CustomObject *self = (CustomObject *) op;
Py_VISIT(self->first);
Py_VISIT(self->last);
return 0;
}
注釈
Py_VISIT() を使うには、tp_traverse の実装で、引数の名前を必ず visit と arg にする必要があります。
次に、参照の循環に加わる可能性のある子オブジェクトをすべてクリアするメソッドを用意する必要があります。
static int
Custom_clear(PyObject *op)
{
CustomObject *self = (CustomObject *) op;
Py_CLEAR(self->first);
Py_CLEAR(self->last);
return 0;
}
Py_CLEAR() マクロを使っていることに注目してください。
これは、参照カウントを減らしながら任意の型のデータ属性をクリアする、推奨される安全な方法です。
属性を NULL に設定する前に、代わりに Py_XDECREF() を呼び出すと、属性のデストラクタがその属性を再び読み取るコードを呼び出す可能性があります (参照の循環がある場合は 特に 注意が必要です)。
注釈
Py_CLEAR() と同じ処理は、次のようにも書けます。
PyObject *tmp;
tmp = self->first;
self->first = NULL;
Py_XDECREF(tmp);
それでも、属性を削除するときは常に Py_CLEAR() を使う方が、はるかに簡単で間違いも少なくなります。
堅牢性を犠牲にしてまで細かな最適化を試みないでください。
メモリ解放関数 Custom_dealloc は、属性のクリア時に任意のコードを呼び出す可能性があります。
つまり、この関数内で循環 GC が実行される可能性があります。
GC は参照カウントがゼロでないことを前提としているため、メンバをクリアする前に PyObject_GC_UnTrack() を呼び出して、GC の追跡対象からオブジェクトを外す必要があります。
PyObject_GC_UnTrack() と Custom_clear を使って書き直したメモリ解放関数は次のとおりです。
static void
Custom_dealloc(PyObject *op)
{
PyObject_GC_UnTrack(op);
(void)Custom_clear(op);
Py_TYPE(op)->tp_free(op);
}
最後に、クラスのフラグに Py_TPFLAGS_HAVE_GC フラグを追加します。
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
これで完了です。 tp_alloc スロットまたは tp_free ハンドラが書かれていれば、それらを循環ガベージコレクションに使えるよう修正すればよいのです。ほとんどの拡張機能は自動的に提供されるバージョンを使うでしょう。
他の型のサブクラスを作る¶
既存の型を継承した新しい拡張型を作成することができます。組み込み型から継承するのは特に簡単です。必要な PyTypeObject を簡単に利用できるからです。それに比べて、 PyTypeObject 構造体を拡張モジュール間で共有するのは難しいです。
この例では、組み込みの list 型を継承した SubList 型を作成します。
新しい型は通常のリストと完全な互換性を持ち、さらに内部カウンタを増やす increment() メソッドを持ちます。
>>> import sublist
>>> s = sublist.SubList(range(3))
>>> s.extend(s)
>>> print(len(s))
6
>>> print(s.increment())
1
>>> print(s.increment())
2
#define PY_SSIZE_T_CLEAN
#include <Python.h>
typedef struct {
PyListObject list;
int state;
} SubListObject;
static PyObject *
SubList_increment(PyObject *op, PyObject *Py_UNUSED(dummy))
{
SubListObject *self = (SubListObject *) op;
self->state++;
return PyLong_FromLong(self->state);
}
static PyMethodDef SubList_methods[] = {
{"increment", SubList_increment, METH_NOARGS,
PyDoc_STR("状態カウンタを増やす")},
{NULL},
};
static int
SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
{
SubListObject *self = (SubListObject *) op;
if (PyList_Type.tp_init(op, args, kwds) < 0)
return -1;
self->state = 0;
return 0;
}
static PyTypeObject SubListType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "sublist.SubList",
.tp_doc = PyDoc_STR("SubList オブジェクト"),
.tp_basicsize = sizeof(SubListObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_init = SubList_init,
.tp_methods = SubList_methods,
};
static int
sublist_module_exec(PyObject *m)
{
SubListType.tp_base = &PyList_Type;
if (PyType_Ready(&SubListType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot sublist_module_slots[] = {
{Py_mod_exec, sublist_module_exec},
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
{0, NULL}
};
static PyModuleDef sublist_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "sublist",
.m_doc = "拡張型を作成するサンプルモジュール。",
.m_size = 0,
.m_slots = sublist_module_slots,
};
PyMODINIT_FUNC
PyInit_sublist(void)
{
return PyModuleDef_Init(&sublist_module);
}
見てのとおり、ソースコードは前の節の Custom の例によく似ています。
主な違いを順に見ていきましょう。
typedef struct {
PyListObject list;
int state;
} SubListObject;
派生型のオブジェクトで最も大きな違いは、先頭のメンバを基底型のオブジェクト構造体にする必要があることです。
基底型の構造体には、すでに先頭に PyObject_HEAD() が含まれています。
Python オブジェクトが SubList のインスタンスである場合、その PyObject * ポインタは PyListObject * と SubListObject * のどちらにも安全にキャストできます。
static int
SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
{
SubListObject *self = (SubListObject *) op;
if (PyList_Type.tp_init(op, args, kwds) < 0)
return -1;
self->state = 0;
return 0;
}
上の例は、基底型の __init__() メソッドを呼び出す方法を示しています。
この方法は、独自の tp_new と tp_dealloc メンバを持つ型を書くときに重要です。
tp_new ハンドラでは、自身の tp_alloc でオブジェクトのメモリを割り当てるのではなく、基底クラスの tp_new を呼び出して、基底クラスに処理を任せるべきです。
PyTypeObject 構造体には、型の具体的な基底クラスを指定する tp_base があります。
プラットフォーム間のコンパイラの違いによる問題があるため、このフィールドに PyList_Type への参照を直接設定することはできません。
Py_mod_exec 関数内で設定してください。
static int
sublist_module_exec(PyObject *m)
{
SubListType.tp_base = &PyList_Type;
if (PyType_Ready(&SubListType) < 0) {
return -1;
}
if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
return -1;
}
return 0;
}
PyType_Ready() を呼び出す前に、型の構造体の tp_base スロットを設定しておく必要があります。
既存の型から派生させる場合、tp_alloc スロットを PyType_GenericNew() で設定する必要はありません。
基底型の割り当て関数が継承されます。
その後の PyType_Ready() の呼び出しと型オブジェクトのモジュールへの追加は、基本的な Custom の例と同じです。
脚注