らんだむな記憶

blogというものを体験してみようか!的なー

Python の関数コール (4)

Python の関数コール (3) - らんだむな記憶 の続き。

前回と同じ test.py を使う。print 関数を含めて呼び出される関数の個数に注意して

(gdb) b Python/ceval.c:5060
(gdb) b Objects/call.c:353

しておいてスタックを進め、no_arg() に関する Objects/call.c:353 でブレークする。この関数は引数を持たないのであった。少し進めつつ argdefs の評価が終わった時点で引数の内容を見る。

(gdb) s
361	    PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
(gdb) s
363	    if (co->co_kwonlyargcount == 0 && nkwargs == 0 &&
(gdb) p nargs
$1 = 0
(gdb) p argdefs
$2 = (PyObject *) 0x0

このことから、この関数は引数の個数が 0 でデフォルト引数の個数も 0 であることが分かる。期待通りだ。

再びスタックを進め、one_arg1 に関する Objects/call.c:353 でブレークする。この関数は引数を 1 つ持つが今回は明示的に引数を渡さずにデフォルト引数 arg=[] を用いて呼び出す。

(gdb) s
361	    PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
(gdb) s
363	    if (co->co_kwonlyargcount == 0 && nkwargs == 0 &&
(gdb) p nargs
$3 = 0
(gdb) p argdefs
$4 = (PyObject *) 0x7f5d637d4df0

今度は何かしらデフォルト引数を持っていることが分かる。ところでこの PyObjectPython のデータ型の基底クラスであり、https://github.com/python/cpython/blob/v3.9.3/Include/object.h#L105-L109 で定義されている

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;

である。gdb

(gdb) p *argdefs
$5 = {ob_refcnt = 1, ob_type = 0x564d0c5d1c40 <PyTuple_Type>}

のようにすると、ざっくりと中身は見えて、正体は tuple の類であることが分かる。実は https://github.com/python/cpython/blob/v3.9.3/Include/cpython/tupleobject.h#L9-L15 で定義されている

typedef struct {
    PyObject_VAR_HEAD
    /* ob_item contains space for 'ob_size' elements.
       Items must normally not be NULL, except during construction when
       the tuple is not yet visible outside the function that builds it. */
    PyObject *ob_item[1];
} PyTupleObject;

なので、これにキャストして中身を詳細に見てみる。ちなみにこの構造体をもう少しだけ展開すると、

typedef struct {
    struct {
        PyObject ob_base;
        Py_ssize_t ob_size; /* Number of items in variable part */
    } ob_base;
    PyObject *ob_item[1];
} PyTupleObject;

である。要するに構造体の先頭アドレスに PyObject があるので、これは Python のデータ型の基底クラスであり、PyTupleObjectPyObject を継承したクラスであることが分かるのである。
さて、元に戻ると、

(gdb) p *(PyTupleObject *)argdefs
$6 = {ob_base = {ob_base = {ob_refcnt = 1, 
      ob_type = 0x564d0c5d1c40 <PyTuple_Type>}, ob_size = 1}, ob_item = {
    0x7f5d638a9680}}

ob_size = 1 なので、ob_item は長さ 1 の配列であることが分かる。しかも何かしら 0x7f5d638a9680 のアドレスにオブジェクトを持っている。Objects/call.c に戻ると、この後 https://github.com/python/cpython/blob/v3.9.3/Objects/call.c#L374-L376 から https://github.com/python/cpython/blob/v3.9.3/Objects/call.c#L307-L342 function_code_fastcall の呼び出しに繋がり、このデフォルト引数の tuple は PyObject *const *args の部分で渡されている。続けて f->f_localsplus に詰め直されて、https://github.com/python/cpython/blob/v3.9.3/Python/ceval.c#L917-L3846 で定義された地獄のように長い関数 _PyEval_EvalFrameDefault に流れ着いて Python の関数として評価されることになる。ここから先はさすがに気力がわかない。だが、状況証拠から見て、ob_item の正体は Python のオブジェクト [] であろう。

再びスタックを進め、one_arg2 に関する Objects/call.c:353 でブレークする。この関数は引数を 1 つ持つが今回は明示的に引数を渡さずにデフォルト引数 arg=None を用いて呼び出す。また引数を確認するのだが、

(gdb) s
361	    PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
(gdb) s
363	    if (co->co_kwonlyargcount == 0 && nkwargs == 0 &&
(gdb) p nargs
$7 = 0
(gdb) p *(PyTupleObject *)argdefs
$8 = {ob_base = {ob_base = {ob_refcnt = 2, 
      ob_type = 0x564d0c5d1c40 <PyTuple_Type>}, ob_size = 1}, ob_item = {
    0x564d0c5cf200 <_Py_NoneStruct>}}

今度も ob_item は長さ 1 の配列であるものの、その中身 ob_item[0] の正体を gdb が指し示してくれている。https://github.com/python/cpython/blob/v3.9.3/Include/object.h#L516-L517

PyAPI_DATA(PyObject) _Py_NoneStruct; /* Don't use this directly */
#define Py_None (&_Py_NoneStruct)

である。どう考えても PythonNone である。まさに期待通りである。このことからも先ほどの one_arg1 の場合には [] であったであろうことが分かる。