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__)で調べられる。
対話モードで試してみよう。