2. 新しい型を定義する¶
前の章でふれたように、Python では拡張モジュールを書くプログラマが Python のコードから操作できる、新しい型を定義できるようになっています。ちょうど Python の中核にある文字列やリストをつくれるようなものです。
これはそんなにむずかしくはありません。拡張型のためのコードにはすべて、一定のパターンが存在しています。しかし始める前に、いくつか細かいことを理解しておく必要があるでしょう。
2.1. 基本的なこと¶
Python ランタイムは Python の全てのオブジェクトを PyObject*
型の変数と見なします。 PyObject*
は Python の全てのオブジェクトの “基礎型 (base type)” となっています。 PyObject
自身は参照カウントと、オブジェクトの “タイプオブジェクト (type object)” へのポインタのみを持ちます。ここには動作が定義されています; 型オブジェクトは、例えば、ある属性があるオブジェクトから検索されたり、他のオブジェクトによって操作されたりしたときに、どの (C) 関数が呼ばれるのかを決定します。これらの C 関数は “タイプメソッド (type method)” と呼ばれます。
なので、新しいオブジェクトの型を定義したいときは、新しいタイプオブジェクトを作成すればよいわけです。
この手のことは例を見たほうが早いでしょうから、ここに最小限の、しかし完全な、新しい型を定義するモジュールをあげておきます:
#include <Python.h>
typedef struct {
PyObject_HEAD
/* Type-specific fields go here. */
} noddy_NoddyObject;
static PyTypeObject noddy_NoddyType = {
PyVarObject_HEAD_INIT(NULL, 0)
"noddy.Noddy", /* tp_name */
sizeof(noddy_NoddyObject), /* tp_basicsize */
0, /* tp_itemsize */
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Noddy objects", /* tp_doc */
};
static PyModuleDef noddymodule = {
PyModuleDef_HEAD_INIT,
"noddy",
"Example module that creates an extension type.",
-1,
NULL, NULL, NULL, NULL, NULL
};
PyMODINIT_FUNC
PyInit_noddy(void)
{
PyObject* m;
noddy_NoddyType.tp_new = PyType_GenericNew;
if (PyType_Ready(&noddy_NoddyType) < 0)
return NULL;
m = PyModule_Create(&noddymodule);
if (m == NULL)
return NULL;
Py_INCREF(&noddy_NoddyType);
PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
return m;
}
さしあたって覚えておくことは以上ですが、これで前の章からすこしは説明がわかりやすくなっていることと思います。
最初に習うのは、つぎのようなものです:
typedef struct {
PyObject_HEAD
} noddy_NoddyObject;
これが Noddy オブジェクトの内容です — このケースでは、すべての Python オブジェクトが持っているものと何ら変わりはありません — ob_base
と呼ばれる PyObject
型のフィールドです。さらに PyObject
は、 ob_refcnt
フィールドと型オブジェクトへのポインタを持っています。これらは、マクロ Py_REFCNT
と Py_TYPE
を使ってそれぞれアクセスできます。これらは PyObject_HEAD
マクロによって展開されるメンバです。マクロを使う理由は、レイアウトを標準化するためと、デバッグ用ビルド時に特別なデバッグ用のメンバを定義できるようにするためです。
この PyObject_HEAD
マクロの後にはセミコロンがないことに注意してください。セミコロンはすでにマクロ内に含まれています。うっかり後にセミコロンをつけてしまわないように気をつけて。これはお使いの機種では何の問題も起こらないかもしれませんが、機種によっては、おそらく問題になるのです! (Windows 上では、MS Visual C がこの手のエラーを出し、コンパイルできないことが知られています)
比較のため、以下に標準的な Python の浮動小数点数型の定義を見てみましょう:
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
では次にいってみます。かなめの部分、タイプオブジェクトです。
static PyTypeObject noddy_NoddyType = {
PyVarObject_HEAD_INIT(NULL, 0)
"noddy.Noddy", /* tp_name */
sizeof(noddy_NoddyObject), /* tp_basicsize */
0, /* tp_itemsize */
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Noddy objects", /* tp_doc */
};
object.h
の中にある PyTypeObject
の定義を見ると、実際にはここに挙げた以上の数のメンバがあるとわかるでしょう。これ以外のメンバは C コンパイラによってゼロに初期化されるので、必要な時を除いてふつうはそれらの値を明示的には指定せずにおきます。
次のものは非常に重要なので、とくに最初の最初に見ておきましょう:
PyVarObject_HEAD_INIT(NULL, 0)
これはちょっとぶっきらぼうですね。実際に書きたかったのはこうです:
PyVarObject_HEAD_INIT(&PyType_Type, 0)
この場合、タイプオブジェクトの型は「type」という名前になりますが、これは厳密には C の基準に従っておらず、コンパイラによっては文句を言われます。幸いにも、このメンバは PyType_Ready()
が埋めてくれます。
"noddy.Noddy", /* tp_name */
これは型の名前です。この名前はオブジェクトのデフォルトの表現形式と、いくつかのエラーメッセージ中で使われます。たとえば:
>>> "" + noddy.new_noddy()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: cannot add type "noddy.Noddy" to string
注意: この名前はドットで区切られた名前で、モジュール名とそのモジュール内での型名の両方を含んでいます。
この場合のモジュールは noddy
で型は Noddy
ですから、ここでの型名としては noddy.Noddy
を指定するわけです。
ドットで区切られていない名前を使うと、文書ツールの pydoc がその新しい型をモジュールの文書に載せなくなるという副作用があります。
sizeof(noddy_NoddyObject), /* tp_basicsize */
これによって Python は PyObject_New()
が呼ばれたときにどれくらいの量のメモリを割り当てればよいのか知ることができます。
注釈
あなたのタイプを Python でサブクラス化可能にしたい場合、そのタイプが基底タイプと同じ tp_basicsize
をもっていると多重継承のときに問題が生じることがあります。そのタイプを Python のサブクラスにしたとき、その __bases__
リストにはあなたのタイプが最初にくるようにしなければなりません。さもないとエラーの発生なしにあなたのタイプの __new__()
メソッドを呼び出すことはできなくなります。この問題を回避するには、つねにあなたのタイプの tp_basicsize
をその基底タイプよりも大きくしておくことです。ほとんどの場合、あなたのタイプは object
か、そうでなければ基底タイプにデータ用のメンバを追加したものでしょうから、したがって大きさはつねに増加するためこの条件は満たされています。
0, /* tp_itemsize */
これはリストや文字列などの可変長オブジェクトのためのものです。今のところ無視しましょう。
このあとのいくつかのタイプメソッドは使わないのでとばして、クラスのフラグ (flags) には Py_TPFLAGS_DEFAULT
を入れます。
Py_TPFLAGS_DEFAULT, /* tp_flags */
すべての型はフラグにこの定数を含めておく必要があります。これは最低でも Python 3.3 までに定義されているすべてのメンバを許可します。それ以上のメンバが必要なら、対応するフラグの OR をとる必要があります。
この型の docstring は tp_doc
に入れます。
"Noddy objects", /* tp_doc */
ここからタイプメソッドに入るわけですが。ここがあなたのオブジェクトが他と違うところです。でも今回のバージョンでは、これらはどれも実装しないでおき、あとでこの例をより面白いものに改造することにしましょう。
とりあえずやりたいのは、この Noddy
オブジェクトを新しく作れるようにすることです。
オブジェクトの作成を許可するには、 tp_new
の実装を提供する必要があります。
今回は、 API 関数によって提供されるデフォルトの実装 PyType_GenericNew()
を使うだけにしましょう。
これを単に tp_new
スロットに代入すればよいのですが、これは互換上の理由からできません。プラットフォームやコンパイラによっては、構造体メンバの初期化に別の場所で定義されている C の関数を代入することはできないのです。なので、この tp_new
の値はモジュール初期化用の関数で代入します。
PyType_Ready()
を呼ぶ直前です:
noddy_NoddyType.tp_new = PyType_GenericNew;
if (PyType_Ready(&noddy_NoddyType) < 0)
return;
これ以外のタイプメソッドはすべて NULL です。これらについては後ほどふれます!
このファイル中にある他のものは、どれもおなじみでしょう。 PyInit_noddy()
のこれを除いて:
if (PyType_Ready(&noddy_NoddyType) < 0)
return;
この関数は、上で NULL に指定していた ob_type
などのいくつものメンバを埋めて、 Noddy
型を初期化します。
PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
これはこの型をモジュール中の辞書に埋め込みます。これで、 Noddy
クラスを呼べば Noddy
インスタンスを作れるようになりました:
>>> import noddy
>>> mynoddy = noddy.Noddy()
これだけです! 残るはこれをどうやってビルドするかということです。上のコードを noddy.c
というファイルに入れて、以下のものを setup.py
というファイルに入れましょう
from distutils.core import setup, Extension
setup(name="noddy", version="1.0",
ext_modules=[Extension("noddy", ["noddy.c"])])
そして、シェルから以下のように入力します
$ python setup.py build
これでサブディレクトリの下にファイル noddy.so
が作成されます。このディレクトリに移動して Python を起動しましょう。 import noddy
して Noddy オブジェクトで遊べるようになっているはずです。
そんなにむずかしくありません、よね?
もちろん、現在の Noddy 型はまだおもしろみに欠けています。何もデータを持ってないし、何もしてはくれません。継承してサブクラスを作ることさえできないのです。
2.1.1. 基本のサンプルにデータとメソッドを追加する¶
この基本のサンプルにデータとメソッドを追加してみましょう。ついでに、この型を基底クラスとしても利用できるようにします。ここでは新しいモジュール noddy2
をつくり、以下の機能を追加します:
#include <Python.h>
#include "structmember.h"
typedef struct {
PyObject_HEAD
PyObject *first; /* first name */
PyObject *last; /* last name */
int number;
} Noddy;
static void
Noddy_dealloc(Noddy* self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject*)self);
}
static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
Noddy *self;
self = (Noddy *)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;
}
static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
PyObject *first=NULL, *last=NULL, *tmp;
static char *kwlist[] = {"first", "last", "number", NULL};
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;
}
static PyMemberDef Noddy_members[] = {
{"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
"first name"},
{"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
"last name"},
{"number", T_INT, offsetof(Noddy, number), 0,
"noddy number"},
{NULL} /* Sentinel */
};
static PyObject *
Noddy_name(Noddy* self)
{
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 Noddy_methods[] = {
{"name", (PyCFunction)Noddy_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* Sentinel */
};
static PyTypeObject NoddyType = {
PyVarObject_HEAD_INIT(NULL, 0)
"noddy.Noddy", /* tp_name */
sizeof(Noddy), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)Noddy_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_BASETYPE, /* tp_flags */
"Noddy objects", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Noddy_methods, /* tp_methods */
Noddy_members, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Noddy_init, /* tp_init */
0, /* tp_alloc */
Noddy_new, /* tp_new */
};
static PyModuleDef noddy2module = {
PyModuleDef_HEAD_INIT,
"noddy2",
"Example module that creates an extension type.",
-1,
NULL, NULL, NULL, NULL, NULL
};
PyMODINIT_FUNC
PyInit_noddy2(void)
{
PyObject* m;
if (PyType_Ready(&NoddyType) < 0)
return NULL;
m = PyModule_Create(&noddy2module);
if (m == NULL)
return NULL;
Py_INCREF(&NoddyType);
PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
return m;
}
このバージョンでは、いくつもの変更をおこないます。
以下の include を追加します:
#include <structmember.h>
すこしあとでふれますが、この include には属性を扱うための宣言が入っています。
Noddy
オブジェクトの構造体の名前は Noddy
に縮めることにします。タイプオブジェクト名は NoddyType
に縮めます。
これから Noddy
型は 3つのデータ属性をもつようになります。 first 、 last 、および number です。 first と last 属性はファーストネームとラストネームを格納した Python 文字列で、 number 属性は整数の値です。
これにしたがうと、オブジェクトの構造体は次のようになります:
typedef struct {
PyObject_HEAD
PyObject *first;
PyObject *last;
int number;
} Noddy;
いまや管理すべきデータができたので、オブジェクトの割り当てと解放に際してはより慎重になる必要があります。最低限、オブジェクトの解放メソッドが必要です:
static void
Noddy_dealloc(Noddy* self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject*)self);
}
この関数は tp_dealloc
メンバに代入されます。
(destructor)Noddy_dealloc, /*tp_dealloc*/
このメソッドでやっているのは、ふたつの Python 属性の参照カウントを減らすことです。 first
メンバと last
メンバが NULL かもしれないため、ここでは Py_XDECREF()
を使いました。このあとそのオブジェクトのタイプメソッドである tp_free
メンバを呼び出しています。ここではオブジェクトの型が NoddyType
とは限らないことに注意してください。なぜなら、このオブジェクトはサブクラス化したインスタンスかもしれないからです。
ファーストネームとラストネームを空文字列に初期化しておきたいので、新しいメソッドを追加することにしましょう:
static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
Noddy *self;
self = (Noddy *)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
メンバとしてインストールします:
Noddy_new, /* tp_new */
この新しいメンバはその型のオブジェクトを (初期化するのではなく) 作成する責任を負っています。Python ではこのメンバは __new__()
メソッドとして見えています。 __new__()
メソッドについての詳しい議論は “Unifying types and classes in Python” という題名の論文を見てください。 new メソッドを実装する理由のひとつは、インスタンス変数の初期値を保証するためです。この例でやりたいのは new メソッドが first
メンバと last
メンバの値を NULL でないようにするということです。もしこれらの初期値が NULL でもよいのであれば、先の例でやったように、new メソッドとして PyType_GenericNew()
を使うこともできたでしょう。 PyType_GenericNew()
はすべてのインスタンス変数のメンバを NULL にします。
この new メソッドは静的なメソッドで、インスタンス化される型と、型が呼び出されたときの引数が渡され、新しいオブジェクトを作成して返します。
new メソッドは常に固定引数 (positional argument) とキーワード引数を取りますが、それらの引数は無視して初期化メソッドにそのまま渡すことがよくあります。
型がサブクラスをサポートしている場合、渡された型が定義している型でないかもしれないことに注意してください。
new メソッドはメモリ割り当てのために tp_alloc
メンバを呼び出します。
tp_alloc
スロットをこちらで埋める必要はありません。
これは PyType_Ready()
が基底クラス (デフォルトでは object
) をもとに埋めるものです。ほとんどの型ではデフォルトのメモリ割り当てを使っています。
注釈
もし協力的な tp_new
(基底タイプの tp_new
または __new__()
を呼んでいるもの) を作りたいのならば、実行時のメソッド解決順序をつかってどのメソッドを呼びだすかを決定しようとしては いけません 。つねに呼び出す型を静的に決めておき、直接その tp_new
を呼び出すか、あるいは type->tp_base->tp_new
を経由してください。こうしないと、あなたが作成したタイプの Python サブクラスが他の Python で定義されたクラスも継承している場合にうまく動かない場合があります。 (とりわけ、そのようなサブクラスのインスタンスを TypeError
を出さずに作ることが不可能になります。)
つぎに初期化用の関数を見てみましょう:
static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
PyObject *first=NULL, *last=NULL, *tmp;
static char *kwlist[] = {"first", "last", "number", NULL};
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
メンバに代入されます。
(initproc)Noddy_init, /* tp_init */
Python では tp_init
メンバは __init__()
メソッドとして見えています。
このメソッドは、オブジェクトが作成されたあとに、それを初期化する目的で使われます。
new メソッドとはちがって、初期化メソッドは必ず呼ばれるとは限りません。
初期化メソッドはオブジェクトを非 pickle 化するときは呼ばれず、上書きもできます。
初期化メソッドは、インスタンスの初期値を提供するのに必要な引数を受けとります。
このメソッドはつねに固定引数とキーワード引数を受けとります。
初期化メソッドは成功のときは 0 、エラーのときは -1 を返すべきです。
初期化メソッドは複数回呼び出される可能性があります。あなたのオブジェクトの __init__()
メソッドは、誰にでも呼び出すことができるからです。このため、新しい値を代入するさいには特別な注意を払う必要があります。たとえば、 first
メンバには以下のように代入したくなるかもしれません:
if (first) {
Py_XDECREF(self->first);
Py_INCREF(first);
self->first = first;
}
しかしこのやり方は危険です。このタイプでは first
メンバに入るオブジェクトをなにも限定していないので、どんなオブジェクトでもとり得てしまうからです。それはこのコードが first
メンバにアクセスしようとする前に、そのデストラクタが呼び出されてしまうかもしれないのです。このような可能性からパラノイア的に身をまもるため、ほとんどの場合メンバへの代入は,その参照カウントを減らす前におこなってください。こうする必要がないのはどんな場合でしょうか?
その参照カウントが 1 より大きいと確信できる場合。
そのオブジェクトの解放があなたのタイプのコードにコールバックするようなことが決してない場合 [1] 。
ガベージコレクションがサポートされていない場合に
tp_dealloc
ハンドラで参照カウントを減らすとき [2] 。
ここではインスタンス変数を属性として見えるようにしたいのですが、これにはいくつもの方法があります。もっとも簡単な方法は、メンバの定義を与えることです:
static PyMemberDef Noddy_members[] = {
{"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
"first name"},
{"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
"last name"},
{"number", T_INT, offsetof(Noddy, number), 0,
"noddy number"},
{NULL} /* Sentinel */
};
そして、この定義を tp_members
スロットに入れましょう:
Noddy_members, /* tp_members */
各メンバの定義はそれぞれ、メンバの名前、型、オフセット、アクセスフラグおよび docstring です。詳しくは後の “総称的な属性を管理する” (総称的な属性を管理する) の節をご覧ください。
この方法の欠点は、Python 属性に代入できるオブジェクトの型を制限する方法がないことです。ここではファーストネーム first とラストネーム last に、ともに文字列が入るよう期待していますが、今のやり方ではどんな Python オブジェクトも代入できてしまいます。加えてこの属性は削除 (del) できてしまい、その場合、 C のポインタには NULL が設定されます。たとえもしメンバが NULL 以外の値に初期化されるようにしてあったとしても、属性が削除されればメンバは NULL になってしまいます。
ここでは name()
と呼ばれるメソッドを定義しましょう。これはファーストネーム first とラストネーム last を連結した文字列をそのオブジェクトの名前として返します。
static PyObject *
Noddy_name(Noddy* self)
{
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);
}
このメソッドは C 関数として実装され、 Noddy
(あるいは Noddy
のサブクラス) のインスタンスを第一引数として受けとります。メソッドはつねにそのインスタンスを最初の引数として受けとらなければなりません。しばしば固定引数とキーワード引数も受けとりますが、今回はなにも必要ないので、固定引数のタプルもキーワード引数の辞書も取らないことにします。このメソッドは Python の以下のメソッドと等価です:
def name(self):
return "%s %s" % (self.first, self.last)
first
メンバと last
メンバがそれぞれ NULL かどうかチェックしなければならないことに注意してください。これらは削除される可能性があり、その場合値は NULL にセットされます。この属性の削除を禁止して、そこに入れられる値を文字列に限定できればなおいいでしょう。次の節ではこれについて扱います。
さて、メソッドを定義したので、ここでメソッド定義用の配列を作成する必要があります:
static PyMethodDef Noddy_methods[] = {
{"name", (PyCFunction)Noddy_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* Sentinel */
};
これを tp_methods
スロットに入れましょう:
Noddy_methods, /* tp_methods */
ここでの METH_NOARGS
フラグは、そのメソッドが引数を取らないことを宣言するのに使われています。
最後に、この型を基底クラスとして利用可能にしましょう。上のメソッドは注意ぶかく書かれているので、これはそのオブジェクトの型が作成されたり利用される場合についてどんな仮定も置いていません。なので、ここですべきことは Py_TPFLAGS_BASETYPE
をクラス定義のフラグに加えるだけです:
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
PyInit_noddy()
の名前を PyInit_noddy2()
に変更し、 PyModuleDef
に渡されるモジュール名を更新します。
さいごに setup.py
ファイルを更新して新しいモジュールをビルドします:
from distutils.core import setup, Extension
setup(name="noddy", version="1.0",
ext_modules=[
Extension("noddy", ["noddy.c"]),
Extension("noddy2", ["noddy2.c"]),
])
2.1.2. データ属性をこまかく制御する¶
この節では、 Noddy
クラスの例にあった first
と last
の各属性にたいして、より精密な制御を提供します。以前のバージョンのモジュールでは、インスタンス変数の first
と last
には文字列以外のものも代入できてしまい、あまつさえ削除まで可能でした。ここではこれらの属性が必ず文字列を保持しているようにしましょう。
#include <Python.h>
#include "structmember.h"
typedef struct {
PyObject_HEAD
PyObject *first;
PyObject *last;
int number;
} Noddy;
static void
Noddy_dealloc(Noddy* self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject*)self);
}
static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
Noddy *self;
self = (Noddy *)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;
}
static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
PyObject *first=NULL, *last=NULL, *tmp;
static char *kwlist[] = {"first", "last", "number", NULL};
if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", 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;
}
static PyMemberDef Noddy_members[] = {
{"number", T_INT, offsetof(Noddy, number), 0,
"noddy number"},
{NULL} /* Sentinel */
};
static PyObject *
Noddy_getfirst(Noddy *self, void *closure)
{
Py_INCREF(self->first);
return self->first;
}
static int
Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
{
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_DECREF(self->first);
Py_INCREF(value);
self->first = value;
return 0;
}
static PyObject *
Noddy_getlast(Noddy *self, void *closure)
{
Py_INCREF(self->last);
return self->last;
}
static int
Noddy_setlast(Noddy *self, PyObject *value, void *closure)
{
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_DECREF(self->last);
Py_INCREF(value);
self->last = value;
return 0;
}
static PyGetSetDef Noddy_getseters[] = {
{"first",
(getter)Noddy_getfirst, (setter)Noddy_setfirst,
"first name",
NULL},
{"last",
(getter)Noddy_getlast, (setter)Noddy_setlast,
"last name",
NULL},
{NULL} /* Sentinel */
};
static PyObject *
Noddy_name(Noddy* self)
{
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
static PyMethodDef Noddy_methods[] = {
{"name", (PyCFunction)Noddy_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* Sentinel */
};
static PyTypeObject NoddyType = {
PyVarObject_HEAD_INIT(NULL, 0)
"noddy.Noddy", /* tp_name */
sizeof(Noddy), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)Noddy_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_BASETYPE, /* tp_flags */
"Noddy objects", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Noddy_methods, /* tp_methods */
Noddy_members, /* tp_members */
Noddy_getseters, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Noddy_init, /* tp_init */
0, /* tp_alloc */
Noddy_new, /* tp_new */
};
static PyModuleDef noddy3module = {
PyModuleDef_HEAD_INIT,
"noddy3",
"Example module that creates an extension type.",
-1,
NULL, NULL, NULL, NULL, NULL
};
PyMODINIT_FUNC
PyInit_noddy3(void)
{
PyObject* m;
if (PyType_Ready(&NoddyType) < 0)
return NULL;
m = PyModule_Create(&noddy3module);
if (m == NULL)
return NULL;
Py_INCREF(&NoddyType);
PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
return m;
}
first
属性と last
属性をよりこまかく制御するためには、カスタムメイドの getter 関数と setter 関数を使います。以下は first
属性から値を取得する関数 (getter) と、この属性に値を格納する関数 (setter) です:
Noddy_getfirst(Noddy *self, void *closure)
{
Py_INCREF(self->first);
return self->first;
}
static int
Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
{
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 str");
return -1;
}
Py_DECREF(self->first);
Py_INCREF(value);
self->first = value;
return 0;
}
getter 関数には Noddy
オブジェクトと「閉包 (closure)」 (これは void型のポインタです) が渡されます。今回のケースでは閉包は無視します。 (閉包とは定義データが渡される setter や getter の高度な利用をサポートするためのもので、これを使うとたとえば getter と setter をひとまとめにした関数に、閉包のデータにもとづいて属性を get するか set するか決めさせる、といったことができます。)
setter 関数には Noddy
オブジェクトと新しい値、そして閉包が渡されます。新しい値は NULL かもしれず、その場合はこの属性が削除されます。ここでは属性が削除されたり、その値が文字列でないときにはエラーを発生させるようにします。
ここでは PyGetSetDef
構造体の配列をつくります:
static PyGetSetDef Noddy_getseters[] = {
{"first",
(getter)Noddy_getfirst, (setter)Noddy_setfirst,
"first name",
NULL},
{"last",
(getter)Noddy_getlast, (setter)Noddy_setlast,
"last name",
NULL},
{NULL} /* Sentinel */
};
そしてこれを tp_getset
スロットに登録します:
Noddy_getseters, /* tp_getset */
これで属性の getter と setter が登録できました。
PyGetSetDef
構造体の最後の要素が上で説明した閉包です。今回は閉包は使わないので NULL を渡しています。
また、メンバ定義からはこれらの属性を除いておきましょう:
static PyMemberDef Noddy_members[] = {
{"number", T_INT, offsetof(Noddy, number), 0,
"noddy number"},
{NULL} /* Sentinel */
};
また、ここでは tp_init
ハンドラも渡されるものとして文字列のみを許可するように修正する必要があります [3]:
static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
PyObject *first=NULL, *last=NULL, *tmp;
static char *kwlist[] = {"first", "last", "number", NULL};
if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", 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()
呼び出しに変えられることを意味します。
唯一これを変えられないのはメモリ解放関数 (deallocator) で、なぜならここではコンストラクタによるメンバ初期化が失敗している可能性があるからです。
さて、先ほどもしたように、このモジュール初期化関数と初期化関数内にあるモジュール名を変更しましょう。そして setup.py
ファイルに追加の定義をくわえます。
2.1.3. 循環ガベージコレクションをサポートする¶
Python は循環ガベージコレクション機能をもっており、これは不要なオブジェクトを、たとえ参照カウントがゼロでなくても、発見することができます。これはオブジェクトの参照が循環しているときに起こりえます。たとえば以下の例を考えてください:
>>> l = []
>>> l.append(l)
>>> del l
この例では、自分自身をふくむリストをつくりました。たとえこのリストを del しても、それは自分自身への参照をまだ持ちつづけますから、参照カウントはゼロにはなりません。嬉しいことに Python には循環ガベージコレクション機能がありますから、最終的にはこのリストが不要であることを検出し、解放できます。
Noddy
クラスの 2番目の例では、 first
属性と last
属性にどんなオブジェクトでも格納できるようになっていました。 [4] 。つまり、 Noddy
オブジェクトの参照は循環しうるのです:
>>> import noddy2
>>> n = noddy2.Noddy()
>>> l = [n]
>>> n.first = l
これは実にばかげた例ですが、すくなくとも Noddy
クラスに循環ガベージコレクション機能のサポートを加える口実を与えてくれます。循環ガベージコレクションをサポートするには 2つのタイプスロットを埋め、これらのスロットを許可するようにクラス定義のフラグを設定する必要があります:
#include <Python.h>
#include "structmember.h"
typedef struct {
PyObject_HEAD
PyObject *first;
PyObject *last;
int number;
} Noddy;
static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
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;
}
static int
Noddy_clear(Noddy *self)
{
PyObject *tmp;
tmp = self->first;
self->first = NULL;
Py_XDECREF(tmp);
tmp = self->last;
self->last = NULL;
Py_XDECREF(tmp);
return 0;
}
static void
Noddy_dealloc(Noddy* self)
{
Noddy_clear(self);
Py_TYPE(self)->tp_free((PyObject*)self);
}
static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
Noddy *self;
self = (Noddy *)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;
}
static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
PyObject *first=NULL, *last=NULL, *tmp;
static char *kwlist[] = {"first", "last", "number", NULL};
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;
}
static PyMemberDef Noddy_members[] = {
{"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
"first name"},
{"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
"last name"},
{"number", T_INT, offsetof(Noddy, number), 0,
"noddy number"},
{NULL} /* Sentinel */
};
static PyObject *
Noddy_name(Noddy* self)
{
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 Noddy_methods[] = {
{"name", (PyCFunction)Noddy_name, METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL} /* Sentinel */
};
static PyTypeObject NoddyType = {
PyVarObject_HEAD_INIT(NULL, 0)
"noddy.Noddy", /* tp_name */
sizeof(Noddy), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)Noddy_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_BASETYPE |
Py_TPFLAGS_HAVE_GC, /* tp_flags */
"Noddy objects", /* tp_doc */
(traverseproc)Noddy_traverse, /* tp_traverse */
(inquiry)Noddy_clear, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Noddy_methods, /* tp_methods */
Noddy_members, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Noddy_init, /* tp_init */
0, /* tp_alloc */
Noddy_new, /* tp_new */
};
static PyModuleDef noddy4module = {
PyModuleDef_HEAD_INIT,
"noddy4",
"Example module that creates an extension type.",
-1,
NULL, NULL, NULL, NULL, NULL
};
PyMODINIT_FUNC
PyInit_noddy4(void)
{
PyObject* m;
if (PyType_Ready(&NoddyType) < 0)
return NULL;
m = PyModule_Create(&noddy4module);
if (m == NULL)
return NULL;
Py_INCREF(&NoddyType);
PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
return m;
}
traversal メソッドは循環した参照に含まれる可能性のある内部オブジェクトへのアクセスを提供します:
static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
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;
}
循環した参照に含まれるかもしれない各内部オブジェクトに対して、 traversal メソッドに渡された visit()
関数を呼びます。 visit()
関数は内部オブジェクトと、traversal メソッドに渡された追加の引数 arg を引数としてとります。この関数はこの値が非負の場合に返される整数の値を返します。
Python には、visit 関数の呼び出しを自動化する Py_VISIT()
マクロが用意されています。 Py_VISIT()
を使えば、 Noddy_traverse()
は次のように簡略化できます:
static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
Py_VISIT(self->first);
Py_VISIT(self->last);
return 0;
}
注釈
注意: tp_traverse
の実装で Py_VISIT()
を使うには、その引数に正確に visit および arg という名前をつける必要があります。これは、この退屈な実装に統一性を導入することを促進します。
また、循環した参照に含まれた内部オブジェクトを消去するためのメソッドも提供する必要があります。 メモリ解放関数を再実装して、このメソッドに使いましょう:
static int
Noddy_clear(Noddy *self)
{
PyObject *tmp;
tmp = self->first;
self->first = NULL;
Py_XDECREF(tmp);
tmp = self->last;
self->last = NULL;
Py_XDECREF(tmp);
return 0;
}
static void
Noddy_dealloc(Noddy* self)
{
Noddy_clear(self);
Py_TYPE(self)->tp_free((PyObject*)self);
}
Noddy_clear()
中での一時変数の使い方に注目してください。ここでは、一時変数をつかって各メンバの参照カウントを減らす前にそれらに NULL を代入しています。これは次のような理由によります。すでにお話ししたように、もし参照カウントがゼロになると、このオブジェクトがコールバックされるようになってしまいます。さらに、いまやガベージコレクションをサポートしているため、ガベージコレクション時に実行されるコードについても心配しなくてはなりません。もしガベージコレクションが走っていると、あなたの tp_traverse
ハンドラが呼び出される可能性があります。メンバの参照カウントがゼロになった場合に、その値が NULL に設定されていないと Noddy_traverse()
が呼ばれる機会はありません。
Python には、注意ぶかく参照カウントを減らすためのマクロ Py_CLEAR()
が用意されています。 Py_CLEAR()
を使えば、 Noddy_clear()
は次のように簡略化できます:
static int
Noddy_clear(Noddy *self)
{
Py_CLEAR(self->first);
Py_CLEAR(self->last);
return 0;
}
最後に、 Py_TPFLAGS_HAVE_GC
フラグをクラス定義のフラグに加えます:
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */
これで完了です。 tp_alloc
スロットまたは tp_free
スロットが書かれていれば、それらを循環ガベージコレクションに使えるよう修正すればよいのです。ほとんどの拡張機能は自動的に提供されるバージョンを使うでしょう。
2.1.4. 他の型のサブクラスを作る¶
既存の型を継承した新しい拡張型を作成することができます。組み込み型から継承するのは特に簡単です。必要な PyTypeObject
を簡単に利用できるからです。それに比べて、 PyTypeObject
構造体を拡張モジュール間で共有するのは難しいです。
次の例では、ビルトインの list
型を継承した Shoddy
型を作成しています。新しい型は通常のリスト型と完全に互換性がありますが、追加で内部のカウンタを増やす increment()
メソッドを持っています。
>>> import shoddy
>>> s = shoddy.Shoddy(range(3))
>>> s.extend(s)
>>> print(len(s))
6
>>> print(s.increment())
1
>>> print(s.increment())
2
#include <Python.h>
typedef struct {
PyListObject list;
int state;
} Shoddy;
static PyObject *
Shoddy_increment(Shoddy *self, PyObject *unused)
{
self->state++;
return PyLong_FromLong(self->state);
}
static PyMethodDef Shoddy_methods[] = {
{"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,
PyDoc_STR("increment state counter")},
{NULL, NULL},
};
static int
Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)
{
if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)
return -1;
self->state = 0;
return 0;
}
static PyTypeObject ShoddyType = {
PyVarObject_HEAD_INIT(NULL, 0)
"shoddy.Shoddy", /* tp_name */
sizeof(Shoddy), /* tp_basicsize */
0, /* tp_itemsize */
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT |
Py_TPFLAGS_BASETYPE, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Shoddy_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Shoddy_init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
};
static PyModuleDef shoddymodule = {
PyModuleDef_HEAD_INIT,
"shoddy",
"Shoddy module",
-1,
NULL, NULL, NULL, NULL, NULL
};
PyMODINIT_FUNC
PyInit_shoddy(void)
{
PyObject *m;
ShoddyType.tp_base = &PyList_Type;
if (PyType_Ready(&ShoddyType) < 0)
return NULL;
m = PyModule_Create(&shoddymodule);
if (m == NULL)
return NULL;
Py_INCREF(&ShoddyType);
PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
return m;
}
見てわかるように、ソースコードは前の節の Noddy
の時と非常に似ています。違う部分をそれぞれを見ていきます。
typedef struct {
PyListObject list;
int state;
} Shoddy;
継承した型のオブジェクトの最初の違いは、親クラスのオブジェクト構造が最初に必要なことです。基底型が既に PyObject_HEAD()
を構造体の先頭に持っています。
Python オブジェクトが Shoddy
型のインスタンスだった場合、その PyObject* ポインタは PyListObject* にも Shoddy* にも安全にキャストできます。
static int
Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)
{
if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)
return -1;
self->state = 0;
return 0;
}
この新しい型の __init__
メソッドで、基底型の __init__
メソッドを呼び出している様子を見ることができます。
このパターンは、カスタムの new
と dealloc
メソッドを実装するときには重要です。継承した型の new
メソッドは、 tp_alloc
を使ってメモリを割り当てるべきではありません。それは基底型の tp_new
を呼出たときに処理されるからです。
Shoddy
型のために PyTypeObject()
を埋めるとき、 tp_base()
スロットを見つけることができます。クロスプラットフォームのコンパイラに対応するために、直接そのスロットを PyList_Type()
で埋めてはいけません。代わりに、後でモジュールの init()
関数の中で行うことができます。
PyMODINIT_FUNC
PyInit_shoddy(void)
{
PyObject *m;
ShoddyType.tp_base = &PyList_Type;
if (PyType_Ready(&ShoddyType) < 0)
return NULL;
m = PyModule_Create(&shoddymodule);
if (m == NULL)
return NULL;
Py_INCREF(&ShoddyType);
PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
return m;
}
PyType_Read()
を呼ぶ前に、型の構造は tp_base
スロットは埋められていなければなりません。継承している新しい型を作るとき、 tp_alloc
スロットを PyType_GenericNew()
で埋める必要はありません。 – 基底型のアロケート関数が継承されます。
この後は、 PyType_Ready()
関数を呼び、タイプオブジェクトをモジュールへ追加するのは、基本的な Noddy
の例と同じです。
2.2. タイプメソッド¶
この節ではさまざまな実装可能なタイプメソッドと、それらが何をするものであるかについて、ざっと説明します。
以下は PyTypeObject
の定義です。デバッグビルドでしか使われないいくつかのメンバは省いてあります:
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
/* Methods to implement standard operations */
destructor tp_dealloc;
printfunc tp_print;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
or tp_reserved (Python 3) */
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;
/* Functions to access object as input/output buffer */
PyBufferProcs *tp_as_buffer;
/* Flags to define presence of optional/expanded features */
unsigned long tp_flags;
const char *tp_doc; /* Documentation string */
/* call function for all accessible objects */
traverseproc tp_traverse;
/* delete references to contained objects */
inquiry tp_clear;
/* rich comparisons */
richcmpfunc tp_richcompare;
/* weak reference enabler */
Py_ssize_t tp_weaklistoffset;
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;
/* Attribute descriptor and subclassing stuff */
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
struct _typeobject *tp_base;
PyObject *tp_dict;
descrgetfunc tp_descr_get;
descrsetfunc tp_descr_set;
Py_ssize_t tp_dictoffset;
initproc tp_init;
allocfunc tp_alloc;
newfunc tp_new;
freefunc tp_free; /* Low-level free-memory routine */
inquiry tp_is_gc; /* For PyObject_IS_GC */
PyObject *tp_bases;
PyObject *tp_mro; /* method resolution order */
PyObject *tp_cache;
PyObject *tp_subclasses;
PyObject *tp_weaklist;
destructor tp_del;
/* Type attribute cache version tag. Added in version 2.6 */
unsigned int tp_version_tag;
destructor tp_finalize;
} PyTypeObject;
たくさんの メソッドがありますね。でもそんなに心配する必要はありません。定義したい型があるなら、実装するのはこのうちのごくわずかですむことがほとんどです。
すでに予想されているでしょうが、これらの多様なハンドラについて、これからより詳しい情報を提供します。しかしこれらのメンバが構造体中で定義されている順番は無視します。というのは、これらのメンバの現れる順序は歴史的な遺産によるものだからです。型を初期化するさいに、これらのメンバを正しい順序で並べるよう、くれぐれも注意してください。ふつういちばん簡単なのは、必要なメンバがすべて含まれている (たとえそれらが 0
に初期化されていても) 例をとってきて、自分の型に合わせるよう変更をくわえることです。
const char *tp_name; /* For printing */
これは型の名前です。前節で説明したように、これはいろいろな場面で現れ、ほとんどは診断用の目的で使われるものです。なので、そのような場面で役に立つであろう名前を選んでください!
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
これらのメンバは、この型のオブジェクトが作成されるときにどれだけのメモリを割り当てればよいのかをランタイムに指示します。Python には可変長の構造体 (文字列やリストなどを想像してください) に対する組み込みのサポートがある程度あり、ここで tp_itemsize
メンバが使われます。これらについてはあとでふれます。
const char *tp_doc;
ここには Python スクリプトリファレンス obj.__doc__
が doc string を返すときの文字列 (あるいはそのアドレス) を入れます。
では次に、ほとんどの拡張型が実装するであろう基本的なタイプメソッドに入っていきます。
2.2.1. ファイナライズとメモリ解放¶
destructor tp_dealloc;
型のインスタンスの参照カウントがゼロになり、Python インタプリタがそれを潰して再利用したくなると、この関数が呼ばれます。解放すべきメモリをその型が保持していたり、それ以外にも実行すべき後処理がある場合は、それらをここに入れられます。オブジェクトそれ自体もここで解放される必要があります。この関数の例は、以下のようなものです:
static void
newdatatype_dealloc(newdatatypeobject * obj)
{
free(obj->obj_UnderlyingDatatypePtr);
Py_TYPE(obj)->tp_free(obj);
}
メモリ解放関数でひとつ重要なのは、処理待ちの例外にいっさい手をつけないことです。なぜなら、解放用の関数は Python インタプリタがスタックを元の状態に戻すときに呼ばれることが多いからです。そして (通常の関数からの復帰でなく) 例外のためにスタックが巻き戻されるときは、すでに発生している例外からメモリ解放関数を守るものはありません。解放用の関数がおこなう動作が追加の Python のコードを実行してしまうと、それらは例外が発生していることを検知するかもしれません。これはインタプリタが誤解させるエラーを発生させることにつながります。これを防ぐ正しい方法は、安全でない操作を実行する前に処理待ちの例外を保存しておき、終わったらそれを元に戻すことです。これは PyErr_Fetch()
および PyErr_Restore()
関数を使うことによって可能になります:
static void
my_dealloc(PyObject *obj)
{
MyObject *self = (MyObject *) obj;
PyObject *cbresult;
if (self->my_callback != NULL) {
PyObject *err_type, *err_value, *err_traceback;
/* This saves the current exception state */
PyErr_Fetch(&err_type, &err_value, &err_traceback);
cbresult = PyObject_CallObject(self->my_callback, NULL);
if (cbresult == NULL)
PyErr_WriteUnraisable(self->my_callback);
else
Py_DECREF(cbresult);
/* This restores the saved exception state */
PyErr_Restore(err_type, err_value, err_traceback);
Py_DECREF(self->my_callback);
}
Py_TYPE(obj)->tp_free((PyObject*)self);
}
注釈
メモリ解放関数の中で安全に行えることにはいくつか制限があります。
1つ目は、その型が (tp_traverse
および tp_clear
を使って) ガベージコレクションをサポートしている場合、 tp_dealloc
が呼び出されるまでに、消去されファイナライズされてしまうオブジェクトのメンバーが有り得ることです。
2つ目は、 tp_dealloc
の中ではオブジェクトは不安定な状態にあることです: つまり参照カウントが0であるということです。
(上の例にあるような) 複雑なオブジェクトや API の呼び出しでは、 tp_dealloc
を再度呼び出し、二重解放からクラッシュすることになるかもしれません。
Python 3.4 からは、複雑なファイナライズのコードは tp_dealloc
に置かず、代わりに新しく導入された tp_finalize
という型メソッドを使うことが推奨されています。
参考
PEP 442 で新しいファイナライズの仕組みが説明されています。
2.2.2. オブジェクト表現¶
Python では、オブジェクトの文字列表現を生成するのに 2つのやり方があります: repr()
関数を使う方法と、 str()
関数を使う方法です。 (print()
関数は単に str()
を呼び出します。) これらのハンドラはどちらも省略できます。
reprfunc tp_repr;
reprfunc tp_str;
tp_repr
ハンドラは呼び出されたインスタンスの文字列表現を格納した文字列オブジェクトを返す必要があります。簡単な例は以下のようなものです:
static PyObject *
newdatatype_repr(newdatatypeobject * obj)
{
return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:\%d}}",
obj->obj_UnderlyingDatatypePtr->size);
}
tp_repr
ハンドラが指定されていなければ、インタプリタはその型の tp_name
とそのオブジェクトの一意な識別値をもちいて文字列表現を作成します。
tp_str
ハンドラと str()
の関係は、上の tp_repr
ハンドラと repr()
の関係に相当します。つまり、これは Python のコードがオブジェクトのインスタンスに対して str()
を呼び出したときに呼ばれます。この関数の実装は tp_repr
ハンドラのそれと非常に似ていますが、得られる文字列表現は人間が読むことを意図されています。 tp_str
が指定されていない場合、かわりに tp_repr
ハンドラが使われます。
以下は簡単な例です:
static PyObject *
newdatatype_str(newdatatypeobject * obj)
{
return PyUnicode_FromFormat("Stringified_newdatatype{{size:\%d}}",
obj->obj_UnderlyingDatatypePtr->size);
}
2.2.3. 属性を管理する¶
属性をもつどのオブジェクトに対しても、その型は、それらオブジェクトの属性をどのように解決するか制御する関数を提供する必要があります。必要な関数としては、属性を (それが定義されていれば) 取り出すものと、もうひとつは属性に (それが許可されていれば) 値を設定するものです。属性を削除するのは特殊なケースで、この場合は新しい値としてハンドラに NULL が渡されます。
Python は 2つの属性ハンドラの組をサポートしています。属性をもつ型はどちらか一組を実装するだけでよく、それらの違いは一方の組が属性の名前を char*
として受け取るのに対してもう一方の組は属性の名前を PyObject*
として受け取る、というものです。それぞれの型はその実装にとって都合がよい方を使えます。
getattrfunc tp_getattr; /* char * version */
setattrfunc tp_setattr;
/* ... */
getattrofunc tp_getattro; /* PyObject * version */
setattrofunc tp_setattro;
オブジェクトの属性へのアクセスがつねに (すぐあとで説明する) 単純な操作だけならば、 PyObject*
を使って属性を管理する関数として、総称的 (generic) な実装を使えます。特定の型に特化した属性ハンドラの必要性は Python 2.2 からほとんど完全になくなりました。しかし、多くの例はまだ、この新しく使えるようになった総称的なメカニズムを使うよう更新されてはいません。
2.2.3.1. 総称的な属性を管理する¶
ほとんどの型は 単純な 属性を使うだけです。では、どのような属性が単純だといえるのでしょうか? それが満たすべき条件はごくわずかです:
PyType_Ready()
が呼ばれたとき、すでに属性の名前がわかっていること。属性を参照したり設定したりするときに、特別な記録のための処理が必要でなく、また参照したり設定した値に対してどんな操作も実行する必要がないこと。
これらの条件は、属性の値や、値が計算されるタイミング、または格納されたデータがどの程度妥当なものであるかといったことになんら制約を課すものではないことに注意してください。
PyType_Ready()
が呼ばれると、これはそのタイプオブジェクトに参照されている 3つのテーブルを使って、そのタイプオブジェクトの辞書中にデスクリプタ(descriptor) を作成します。各デスクリプタは、インスタンスオブジェクトの属性に対するアクセスを制御します。それぞれのテーブルはなくてもかまいません。もしこれら 3つがすべて NULL だと、その型のインスタンスはその基底型から継承した属性だけを持つことになります。また、 tp_getattro
および tp_setattro
が NULL のままだった場合も、基底型にこれらの属性の操作がまかせられます。
テーブルはタイプオブジェクト中の 3つのメンバとして宣言されています:
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
tp_methods
が NULL でない場合、これは PyMethodDef
構造体への配列を指している必要があります。テーブル中の各エントリは、つぎのような構造体のインスタンスです:
typedef struct PyMethodDef {
const char *ml_name; /* method name */
PyCFunction ml_meth; /* implementation function */
int ml_flags; /* flags */
const char *ml_doc; /* docstring */
} PyMethodDef;
その型が提供する各メソッドについてひとつのエントリを定義する必要があります。基底型から継承してきたメソッドについてはエントリは必要ありません。これの最後には、配列の終わりを示すための見張り番 (sentinel) として追加のエントリがひとつ必要です。この場合、 ml_name
メンバが sentinel として使われ、その値は NULL でなければなりません。
2番目のテーブルは、インスタンス中に格納されるデータと直接対応づけられた属性を定義するのに使います。いくつもの C の原始的な型がサポートされており、アクセスを読み込み専用にも読み書き可能にもできます。このテーブルで使われる構造体は次のように定義されています:
typedef struct PyMemberDef {
char *name;
int type;
int offset;
int flags;
char *doc;
} PyMemberDef;
このテーブルの各エントリに対してデスクリプタ(descriptor)が作成され、値をインスタンスの構造体から抽出しうる型に対してそれらが追加されます。 type
メンバは structmember.h
ヘッダで定義された型のコードをひとつ含んでいる必要があります。この値は Python における値と C における値をどのように変換しあうかを定めるものです。 flags
メンバはこの属性がどのようにアクセスされるかを制御するフラグを格納するのに使われます。
以下のフラグ用定数は structmember.h
で定義されており、これらはビットごとの OR を取って組み合わせられます。
定数 |
意味 |
---|---|
READONLY |
絶対に変更できない。 |
READ_RESTRICTED |
制限モード (restricted mode) では参照できない。 |
WRITE_RESTRICTED |
制限モード (restricted mode) では変更できない。 |
RESTRICTED |
制限モード (restricted mode) では参照も変更もできない。 |
tp_members
を使ったひとつの面白い利用法は、実行時に使われるデスクリプタを作成しておき、単にテーブル中にテキストを置いておくことによって、この方法で定義されたすべての属性に doc string を関連付けられるようにすることです。アプリケーションはこのイントロスペクション用 API を使って、クラスオブジェクトからデスクリプタを取り出し、その __doc__
属性を使って doc string を得られます。
tp_methods
テーブルと同じように、ここでも name
メンバの値を NULL にした見張り用エントリが必要です。
2.2.3.2. 特定の型に特化した属性の管理¶
話を単純にするため、ここでは char*
を使ったバージョンのみを示します。name パラメータの型はインターフェイスとして char*
を使うか PyObject*
を使うかの違いしかありません。この例では、上の総称的な例と同じことを効率的にやりますが、 Python 2.2 で追加された総称的な型のサポートを使わずにやります。これはハンドラの関数がどのようにして呼ばれるのかを説明します。これで、たとえその機能を拡張する必要があるとき、何をどうすればいいかわかるでしょう。
tp_getattr
ハンドラはオブジェクトが属性への参照を要求するときに呼ばれます。これは、そのクラスの __getattr__()
メソッドが呼ばれるであろう状況と同じ状況下で呼び出されます。
以下に例を示します:
static PyObject *
newdatatype_getattr(newdatatypeobject *obj, char *name)
{
if (strcmp(name, "data") == 0)
{
return PyLong_FromLong(obj->data);
}
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%.400s'",
tp->tp_name, name);
return NULL;
}
tp_setattr
ハンドラは、クラスのインスタンスの __setattr__()
または __delattr__()
メソッドが呼ばれるであろう状況で呼び出されます。ある属性が削除されるとき、3番目のパラメータは NULL になります。以下の例はたんに例外を発生させるものですが、もし本当にこれと同じことをしたいなら、 tp_setattr
ハンドラを NULL に設定すべきです。
static int
newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)
{
(void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);
return -1;
}
2.2.4. オブジェクトの比較¶
richcmpfunc tp_richcompare;
tp_richcompare
ハンドラは比較処理が要求されたときに呼び出されます。
__lt__()
のような 拡張比較メソッド に類似しており、 PyObject_RichCompare()
と PyObject_RichCompareBool()
からも呼び出されます。
この関数は 2 つの Python オブジェクトと演算子を引数に取り、演算子は Py_EQ
, Py_NE
, Py_LE
, Py_GT
, Py_LT
, Py_GT
のどれか 1 つです。関数は指定された演算子に従って 2 つのオブジェクトを比較し、比較に成功した場合の Py_True
および Py_False
か、比較処理が実装されておらず、もう一方のオブジェクトの比較処理を試行することを示す Py_NotImplemented
か、例外が設定された場合の NULL のいづれかを返します。
これは内部ポインタのサイズが等しければ等しいと見なすデータ型のサンプル実装です:
static PyObject *
newdatatype_richcmp(PyObject *obj1, PyObject *obj2, int op)
{
PyObject *result;
int c, size1, size2;
/* code to make sure that both arguments are of type
newdatatype omitted */
size1 = obj1->obj_UnderlyingDatatypePtr->size;
size2 = obj2->obj_UnderlyingDatatypePtr->size;
switch (op) {
case Py_LT: c = size1 < size2; break;
case Py_LE: c = size1 <= size2; break;
case Py_EQ: c = size1 == size2; break;
case Py_NE: c = size1 != size2; break;
case Py_GT: c = size1 > size2; break;
case Py_GE: c = size1 >= size2; break;
}
result = c ? Py_True : Py_False;
Py_INCREF(result);
return result;
}
2.2.5. 抽象的なプロトコルのサポート¶
Python はいくつもの 抽象的な “プロトコル”をサポートしています。これらを使用する特定のインターフェイスについては 抽象オブジェクトレイヤ (abstract objects layer) で解説されています。
これら多数の抽象的なインターフェイスは、Python の実装が開発される初期の段階で定義されていました。とりわけ数値や辞書、そしてシーケンスなどのプロトコルは最初から Python の一部だったのです。それ以外のプロトコルはその後追加されました。型の実装にあるいくつかのハンドラルーチンに依存するようなプロトコルのために、古いプロトコルはハンドラの入ったオプションのブロックとして定義し、型オブジェクトから参照するようになりました。タイプオブジェクトの主部に追加のスロットをもつ新しいプロトコルについては、フラグ用のビットを立てることでそれらのスロットが存在しており、インタプリタがチェックすべきであることを指示できます。(このフラグ用のビットは、そのスロットの値が非 NULL であることを示しているわけではありません。フラグはスロットの存在を示すのに使えますが、そのスロットはまだ埋まっていないかもしれないのです。)
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
お使いのオブジェクトを数値やシーケンス、あるいは辞書のようにふるまうようにしたいならば、それぞれに C の PyNumberMethods
構造体、 PySequenceMethods
構造体、または PyMappingMethods
構造体のアドレスを入れます。これらに適切な値を入れても入れなくてもかまいません。これらを使った例は Python の配布ソースにある Objects
でみつけることができるでしょう。
hashfunc tp_hash;
この関数は、もし使うのならば、これはお使いの型のインスタンスのハッシュ番号を返すようにします。以下はやや的はずれな例ですが
static long
newdatatype_hash(newdatatypeobject *obj)
{
long result;
result = obj->obj_UnderlyingDatatypePtr->size;
result = result * 3;
return result;
}
ternaryfunc tp_call;
この関数は、その型のインスタンスが「関数として呼び出される」ときに呼ばれます。たとえばもし obj1
にそのインスタンスが入っていて、Python スクリプトで obj1('hello')
を実行したとすると、 tp_call
ハンドラが呼ばれます。
この関数は 3つの引数をとります:
arg1 にはその呼び出しの対象となる、そのデータ型のインスタンスが入ります。たとえば呼び出しが
obj1('hello')
の場合、 arg1 はobj1
になります。arg2 は呼び出しの引数を格納しているタプルです。ここから引数を取り出すには
PyArg_ParseTuple()
を使います。arg3 はキーワード引数のための辞書です。これが NULL 以外でキーワード引数をサポートしているなら、
PyArg_ParseTupleAndKeywords()
をつかって引数を取り出せます。キーワード引数をサポートしていないのにこれが NULL 以外の場合は、キーワード引数はサポートしていない旨のメッセージとともにTypeError
を発生させてください。
以下はこの call 関数をてきとうに使った例です。
/* Implement the call function.
* obj1 is the instance receiving the call.
* obj2 is a tuple containing the arguments to the call, in this
* case 3 strings.
*/
static PyObject *
newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)
{
PyObject *result;
char *arg1;
char *arg2;
char *arg3;
if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
return NULL;
}
result = PyUnicode_FromFormat(
"Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",
obj->obj_UnderlyingDatatypePtr->size,
arg1, arg2, arg3);
return result;
}
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;
これらの関数はイテレータ用プロトコルをサポートします。オブジェクトが、その (ループ中に順に生成されていくかもしれない) 内容を巡回 (訳注: イテレータでひとつずつ要素をたどっていくこと) するイテレータをサポートしたい場合は、 tp_iter
ハンドラを実装する必要があります。 tp_iter
ハンドラによって返されるオブジェクトは tp_iter
と tp_iternext
の両方を実装する必要があります。どちらのハンドラも、それが呼ばれたインスタンスをひとつだけ引数としてとり、新しい参照を返します。エラーが起きた場合には例外を設定してから NULL を返す必要があります。
巡回可能な要素を表現するオブジェクトに対しては、 tp_iter
ハンドラがイテレータオブジェクトを返す必要があります。イテレータオブジェクトは巡回中の状態を保持する責任をもっています。お互いに干渉しない複数のイテレータの存在を許すようなオブジェクト (リストやタプルがそうです) の場合は、新しいイテレータを作成して返す必要があります。 (巡回の結果生じる副作用のために) 一回だけしか巡回できないオブジェクトの場合は、それ自身への参照を返すようなハンドラと、 tp_iternext
ハンドラも実装する必要があります。ファイルオブジェクトはそのようなイテレータの例です。
イテレータオブジェクトは両方のハンドラを実装する必要があります。 tp_iter
ハンドラはそのイテレータへの新しい参照を返します (これは破壊的にしか巡回できないオブジェクトに対する tp_iter
ハンドラと同じです)。 tp_iternext
ハンドラはその次のオブジェクトがある場合、それへの新しい参照を返します。巡回が終端に達したときは例外を出さずに NULL を返してもいいですし、 StopIteration
を放出してもかまいません。例外を使わないほうがやや速度が上がるかもしれません。実際のエラーが起こったときには、例外を放出して NULL を返す必要があります。
2.2.6. 弱参照(Weak Reference)のサポート¶
Pythonの弱参照実装のひとつのゴールは、どのような(数値のような弱参照による利益を得ない)タイプでもオーバーヘッドなしで弱参照のメカニズムに組み込めるようにすることです。
弱参照可能なオブジェクトの拡張では、弱参照メカニズムのために PyObject*
フィールドをインスタンス構造体に含む必要があります。これはオブジェクトのコンストラクタで NULL に初期化する必要があります。これは対応するタイプの tp_weaklistoffset
フィールドをフィールドのオフセットに設定しなければいけません。たとえば、インスタンスタイプは以下の構造体で定義されます:
typedef struct {
PyObject_HEAD
PyClassObject *in_class; /* The class object */
PyObject *in_dict; /* A dictionary */
PyObject *in_weakreflist; /* List of weak references */
} PyInstanceObject;
インスタンス用に静的に宣言されたタイプオブジェクトはこのように定義されます:
PyTypeObject PyInstance_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
0,
"module.instance",
/* Lots of stuff omitted for brevity... */
Py_TPFLAGS_DEFAULT, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */
};
タイプのコンストラクタは弱参照を NULL に初期化する責任があります:
static PyObject *
instance_new() {
/* Other initialization stuff omitted for brevity */
self->in_weakreflist = NULL;
return (PyObject *) self;
}
ほかに追記すべきことは、デストラクタは弱参照を消すために弱参照のマネージャを呼ぶ必要があることくらいです。これは弱参照リストが NULL でない場合にだけ必要です:
static void
instance_dealloc(PyInstanceObject *inst)
{
/* Allocate temporaries if needed, but do not begin
destruction just yet.
*/
if (inst->in_weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *) inst);
/* Proceed with object destruction normally. */
}
2.2.7. その他いろいろ¶
上にあげたほとんどの関数は、その値として 0
を与えれば省略できることを忘れないでください。それぞれの関数で提供しなければならない型の定義があり、これらは Python の include 用ディレクトリの object.h
というファイルにおさめられています。これは Python の配布ソースに含まれています。
新しいデータ型に何らかのメソッドを実装するやりかたを学ぶには、以下の方法がおすすめです: Python の配布されているソースをダウンロードして展開する。 Objects
ディレクトリへ行き、C のソースファイルから「 tp_
欲しい名前」の文字列で検索する (たとえば tp_print
とか tp_richcompare
のように)。こうすれば実装したい例がみつかるでしょう。
あるオブジェクトが、いま実装している型のインスタンスであるかどうかを確かめたい場合には、 PyObject_TypeCheck()
関数を使ってください。使用例は以下のようなかんじです:
if (! PyObject_TypeCheck(some_object, &MyType)) {
PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");
return NULL;
}
脚注
[1] | これはそのオブジェクトが文字列や実数などの基本タイプであるような時に成り立ちます。 |
[2] | ここで出てきたタイプではガベージコレクションをサポートしていないので、この例では |
[3] | first および last メンバが文字列であるということはわかっているので、いまやそれらの参照カウントを減らすときにはそれほど注意する必要はないように思えるかもしれません。しかし文字列型のサブクラスは依然として受けつけられています。通常の文字列型ならば、解放時にあなたのオブジェクトがコールバックされることはありませんが、文字列型のサブクラスがそうしないという保証はありません。 |
[4] | 3番目のバージョンでさえ、循環を回避できるという保証はされていません。たとえ通常の文字列型なら循環しない場合でも、文字列型のサブクラスをとることが許されていれば、そのタイプでは循環が発生しうるからです。 |