PythonのTrue/False(続き)
前回の続き。
Pythonの記事なのにC言語の記事になってる。
じゃあintやfloat、listなどの__bool__実装ってどこに書かれてるの? という話。
これらはbuilt-inオブジェクトなので、Pythonディレクトリ以下を見ても
それらしきコードは見つからない。
int.pyみたいなモノがあると期待してたのに。
でもdir(int)などとすると、ちゃんと__bool__や__str__があることが確認できる。
というわけで、コンパイルする前のコードを確認してみる。
ソースコードは公式からダウンロードしてくるか、あるいはここで読める。
例えばPython3.3.1の場合、floatの実装は「Python-3.3.1/Objects/floatobject.c」に
書かれている。該当する個所を抜粋すると、、、
static int float_bool(PyFloatObject *v) { return v->ob_fval != 0.0; }
この辺かなー。
ob_fvalってのは、まぁ略されてるけど要するに object_float valueだろう、たぶん。
0.0じゃなければTrueになるようだ。実際の挙動と一致している。
それじゃあコンテナはどうなるんだろう。確かbool([])とかはFalseだった。
listobject.cというモノがあったので、読んでみるが・・・。
あれ?
それらしき記述が見つからない。
list_bool的なものがあると思ったんだけど…。
他のところにまとめて記述されているのかな。
ということで、object.cを読んでみる。
案の定、それっぽいコードが見つかった。抜粋すると・・・
int PyObject_IsTrue(PyObject *v) { Py_ssize_t res; if (v == Py_True) return 1; if (v == Py_False) return 0; if (v == Py_None) return 0; else if (v->ob_type->tp_as_number != NULL && v->ob_type->tp_as_number->nb_bool != NULL) res = (*v->ob_type->tp_as_number->nb_bool)(v); else if (v->ob_type->tp_as_mapping != NULL && v->ob_type->tp_as_mapping->mp_length != NULL) res = (*v->ob_type->tp_as_mapping->mp_length)(v); else if (v->ob_type->tp_as_sequence != NULL && v->ob_type->tp_as_sequence->sq_length != NULL) res = (*v->ob_type->tp_as_sequence->sq_length)(v); else return 1; /* if it is negative, it should be either -1 or -2 */ return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int); }
ここのドキュメントによると、PyObjectが真だと判断できれば1を返すようだ。
おぉ、なんだかこれっぽい。色々なパラメータを見て、判定結果を返している。
sq_lengthなどはどこで宣言されてるんだろうか?
listobject.cに戻ってみると・・・
static PySequenceMethods list_as_sequence = { (lenfunc)list_length, /* sq_length */ /*** 以下略 ***/ };
こんな風に書かれていた。ちなみにlist_as_sequenceは、
PyTypeObject PyList_Type = { /*** 省略 ***/ &list_as_sequence, /* tp_as_sequence */ /*** 省略 ***/ };
これがおそらく「v->ob_type」の「ob_type」に相当する。
順に掘っていけばsq_lengthまで辿り着ける。
floatobjectなど数値を表すオブジェクトも、やはりPyObject_IsTrueによって
判定されているんだろうか?
floatobject.cによると、、、
static PyNumberMethods float_as_number = { /*** 省略 ***/ (inquiry)float_bool, /*nb_bool*/ /*** 省略 ***/ };
float_boolはさっき0.0じゃなければ1を返していた関数だった。
こいつが巡り巡ってPyObject_IsTrue関数に影響を与えているようだ。
ちなみに、何がbuilt-inなのかは、dir(__builtins__)で調べられる。
対話モードで試してみよう。