Pythonを読む
はじめに †
ようやく__build_class__関数の実装であるbuiltin___build_class__に行きつきました。今回はこの関数が何をしているのかを見ていきます。
builtin___build_class__は長いので段階的に見ていきます。大きく分けると以下の処理を行っています。
- メタクラスの決定
- クラス(メソッドなど)を定義するコードを実行し属性辞書を作成
- 属性辞書を使ってクラスを作成
メタクラスの決定 †
長くなるので引数チェック(エラー終了)系はカットします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
-
|
|
|
|
|
|
|
|
|
|
-
|
|
!
-
-
!
-
|
-
|
!
|
-
|
|
!
|
|
!
-
-
!
|
|
-
|
|
|
!
!
-
!
| static PyObject *
builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
{
PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns;
PyObject *cls = NULL, *cell = NULL;
Py_ssize_t nargs;
int isclass = 0;
nargs = PyTuple_GET_SIZE(args);
func = PyTuple_GET_ITEM(args, 0);
name = PyTuple_GET_ITEM(args, 1);
bases = PyTuple_GetSlice(args, 2, nargs);
if (kwds == NULL) {
meta = NULL;
mkw = NULL;
}
else {
}
if (meta == NULL) {
if (PyTuple_GET_SIZE(bases) == 0) {
meta = (PyObject *) (&PyType_Type);
}
else {
PyObject *base0 = PyTuple_GET_ITEM(bases, 0);
meta = (PyObject *) (base0->ob_type);
}
Py_INCREF(meta);
isclass = 1;
}
if (isclass) {
winner = (PyObject *)_PyType_CalculateMetaclass((PyTypeObject *)meta,
bases);
if (winner != meta) {
Py_DECREF(meta);
meta = winner;
Py_INCREF(meta);
}
}
|
まずはここまで。kwdsがNULLじゃないときというのは「class Foo(metaclass=Meta)」みたいにメタクラスを指定したときです。
今回はメタクラスは指定しておらず、またスーパークラスも指定していません。この場合、typeがメタクラスになります。メタクラスがtypeに決定されたらisclassは0でいい気がするんですけどね。(実際、_PyType_CalculateMetaclassからはmetaがそのまま返されます)
クラス定義コード実行(属性辞書の作成) †
1
2
3
4
5
6
7
8
9
10
11
12
|
-
-
!
-
!
|
|
|
| prep = _PyObject_GetAttrId(meta, &PyId___prepare__);
if (prep == NULL) {
}
else {
PyObject *pargs[2] = {name, bases};
ns = _PyObject_FastCallDict(prep, pargs, 2, mkw);
Py_DECREF(prep);
}
cell = PyEval_EvalCodeEx(PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), ns,
NULL, 0, NULL, 0, NULL, 0, NULL,
PyFunction_GET_CLOSURE(func));
|
本筋ではない、と言ってもいずれは見ないといけないのでちゃんと追っかけていきましょう。具体的には_PyObject_GetAttrIdのことです。
_PyObject_GetAttrId (Objects/object.c) †
まず_PyObject_GetAttrId。これはPyObject_GetAttrを呼んでるだけです。
1
2
3
4
5
6
7
8
9
10
|
-
|
|
|
|
|
|
!
| PyObject *
_PyObject_GetAttrId(PyObject *v, _Py_Identifier *name)
{
PyObject *result;
PyObject *oname = _PyUnicode_FromId(name);
if (!oname)
return NULL;
result = PyObject_GetAttr(v, oname);
return result;
}
|
続いてPyObject_GetAttr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
-
|
|
-
|
|
|
|
!
|
|
-
|
|
|
|
!
|
|
|
|
!
| PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{
PyTypeObject *tp = Py_TYPE(v);
if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
"attribute name must be string, not '%.200s'",
name->ob_type->tp_name);
return NULL;
}
if (tp->tp_getattro != NULL)
return (*tp->tp_getattro)(v, name);
if (tp->tp_getattr != NULL) {
char *name_str = PyUnicode_AsUTF8(name);
if (name_str == NULL)
return NULL;
return (*tp->tp_getattr)(v, name_str);
}
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%U'",
tp->tp_name, name);
return NULL;
}
|
はい、TypeObjectが出てきました。さらにそのメンバの関数ポインタを使って関数を実行しています。
ここでType_Typeの定義(Objects/typeobject.c)を見てみる。
1
2
3
4
5
| -
|
-
!
|
| PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
(getattrofunc)type_getattro,
(setattrofunc)type_setattro,
|
というわけで、type_getattroが呼び出されることになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
-
|
|
|
|
|
-
|
|
!
|
|
|
|
|
|
|
-
-
!
|
!
|
-
!
|
-
|
|
|
|
|
-
!
|
|
!
|
-
!
| static PyObject *
type_getattro(PyTypeObject *type, PyObject *name)
{
PyTypeObject *metatype = Py_TYPE(type);
PyObject *meta_attribute, *attribute;
descrgetfunc meta_get;
if (type->tp_dict == NULL) {
if (PyType_Ready(type) < 0)
return NULL;
}
meta_get = NULL;
meta_attribute = _PyType_Lookup(metatype, name);
if (meta_attribute != NULL) {
Py_INCREF(meta_attribute);
}
attribute = _PyType_Lookup(type, name);
if (attribute != NULL) {
descrgetfunc local_get = Py_TYPE(attribute)->tp_descr_get;
Py_XDECREF(meta_attribute);
Py_INCREF(attribute);
return attribute;
}
}
|
今の場合、typeはType_Typeが渡されているので、TYPEはType_Typeです(ややこしい)。というわけで、typeもmetatype同じものを指すことになり、結局「return attribute」のところで指定された属性__prepare__が返されるはず。
_PyType_Lookupは基本的にはtp_dictから指定された属性を取得する関数です。基本的じゃない点としてはメソッドキャッシュしたり継承さかのぼって属性探したりとかしているわけですが、今回はあまり関係ないので追っかけるのはやめておきます。
PyType_Ready (Objects/typeobject.c) †
さて、追っかけないといけない方の話をしましょう。PyType_ReadyにてTypeObjectの初期化が行われているようです。
なお、実際には_Py_InitializeEx_Privateの中で_Py_ReadyTypes(Objects/object.c)が呼ばれおり、そこで一連のTypeObjectについてPyType_Readyが呼ばれている、つまりバイトコードを動かす段階まで来ているのであればType_Typeの初期化はすでに完了しているという状態です。
PyType_Readyは長いので、__prepare__がtp_dictに登録される周辺部分だけ確認しましょう。心配しなくても(?)また出てきます。とりあえず__prepare__はここに書かれています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-
|
!
-
-
!
!
-
!
!
| static PyObject *
type_prepare(PyObject *self, PyObject *args, PyObject *kwds)
{
return PyDict_New();
}
static PyMethodDef type_methods[] = {
{"__prepare__", (PyCFunction)type_prepare,
METH_VARARGS | METH_KEYWORDS | METH_CLASS,
PyDoc_STR("__prepare__() -> dict\n"
"used to create the namespace for the class statement")},
{0}
};
|
PyMethodDefは前にbultinでも出てきましたね。type_methodsはType_Type定義時に指定されています。
1
2
3
4
5
6
| -
|
-
!
|
|
| PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
type_members,
type_members,
type_getsets,
|
で、PyType_Ready中の登録してそうなところ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
-
|
|
|
|
!
-
|
|
!
-
|
|
!
-
|
|
!
|
dict = type->tp_dict;
if (dict == NULL) {
dict = PyDict_New();
if (dict == NULL)
goto error;
type->tp_dict = dict;
}
if (add_operators(type) < 0)
goto error;
if (type->tp_methods != NULL) {
if (add_methods(type, type->tp_methods) < 0)
goto error;
}
if (type->tp_members != NULL) {
if (add_members(type, type->tp_members) < 0)
goto error;
}
if (type->tp_getset != NULL) {
if (add_getset(type, type->tp_getset) < 0)
goto error;
}
|
add_methodsに進む。なお、add_operatorはTypeObjectに直接書かれているtp_reprなどの特殊メソッドの追加、add_membersは構造体メンバにアクセスするためのデスクリプタの追加、add_getsetは同じくデスクリプタなんだけど実装を個別に書ける式のようです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
-
|
|
-
|
|
|
|
-
|
!
-
-
!
-
|
!
|
|
|
|
|
|
!
!
| static int
add_methods(PyTypeObject *type, PyMethodDef *meth)
{
PyObject *dict = type->tp_dict;
for (; meth->ml_name != NULL; meth++) {
PyObject *descr;
int err;
if (PyDict_GetItemString(dict, meth->ml_name) && !(meth->ml_flags & METH_COEXIST))
continue;
if (meth->ml_flags & METH_CLASS) {
descr = PyDescr_NewClassMethod(type, meth);
}
else if (meth->ml_flags & METH_STATIC) {
}
else {
descr = PyDescr_NewMethod(type, meth);
}
if (descr == NULL)
return -1;
err = PyDict_SetItemString(dict, meth->ml_name, descr);
Py_DECREF(descr);
if (err < 0)
return -1;
}
return 0;
}
|
あれ?デスクリプタ?先ほどtype_getattroでデスクリプタの場合の処理省略したけどそっちが使われてるんですね。まあともかくもう少し先まで確認。デスクリプタ関連はObject/descrobject.cに書かれています。
1
2
3
4
5
6
7
8
9
10
11
|
-
|
|
|
|
|
|
|
!
| PyObject *
PyDescr_NewClassMethod(PyTypeObject *type, PyMethodDef *method)
{
PyMethodDescrObject *descr;
descr = (PyMethodDescrObject *)descr_new(&PyClassMethodDescr_Type,
type, method->ml_name);
if (descr != NULL)
descr->d_method = method;
return (PyObject *)descr;
}
|
type_getattro再び †
では読み違えていたところに戻りましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
-
-
!
-
|
|
-
-
|
|
!
|
|
!
|
!
|
-
!
|
-
|
|
|
|
|
-
-
!
|
|
!
|
|
|
!
| static PyObject *
type_getattro(PyTypeObject *type, PyObject *name)
{
if (meta_attribute != NULL) {
meta_get = Py_TYPE(meta_attribute)->tp_descr_get;
if (meta_get != NULL && PyDescr_IsData(meta_attribute)) {
return meta_get(meta_attribute, (PyObject *)type,
(PyObject *)metatype);
}
Py_INCREF(meta_attribute);
}
attribute = _PyType_Lookup(type, name);
if (attribute != NULL) {
descrgetfunc local_get = Py_TYPE(attribute)->tp_descr_get;
Py_XDECREF(meta_attribute);
if (local_get != NULL) {
return local_get(attribute, (PyObject *)NULL,
(PyObject *)type);
}
Py_INCREF(attribute);
return attribute;
}
|
前述したように今はtypeもmetatype同じになりますが、metaの方はPyDescr_IsDataの条件(tp_descr_setが定義されていたら真)があるので実際に呼び出されるのはlocal_getの方です。
というわけでdescr_getとして呼び出されるclassmethod_get。エラー処理削ると一行だけです。
1
2
3
4
5
|
-
|
!
| static PyObject *
classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type)
{
return PyCFunction_NewEx(descr->d_method, type, NULL);
}
|
ここでCFunctionObjectにラップされるか。
回り道が長引いたので一旦ここで区切ります。
おわりに †
今回は__build_class__、というよりCでのオブジェクト実装の動作について見てきました。その際、PyTypeObjectに登録されている関数が呼び出されていることがわかりました。
また、PyTypeObjectに直接書かれている特殊メソッドおよび通常のメソッドなどはPyTypeObjectの辞書にデスクリプタとして登録されることがわかりました。デスクリプタじゃなくてもいいように思うのですが何か理由があるのでしょう(何かの理由は、必要があったら見ることにします)