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
今度は何かしらデフォルト引数を持っていることが分かる。ところでこの PyObject
は Python のデータ型の基底クラスであり、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 のデータ型の基底クラスであり、PyTupleObject
は PyObject
を継承したクラスであることが分かるのである。
さて、元に戻ると、
(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)
である。どう考えても Python の None
である。まさに期待通りである。このことからも先ほどの one_arg1
の場合には []
であったであろうことが分かる。