インターナルPython ー PythonのオブジェクトとPyObject構造体の関係
最近,Stackless Pythonの実装を追いかけており,理解したところまでメモがわりに記録しておく.コードを読んでも,詳しいところはすぐ忘れちゃうからね.
読むコードは,Stackless Pythonの3.12(http://www.stackless.com/svn)となる.Stackless Pythonとは言っても,基本的な部分はCPythonと殆ど変わらないので,Pythonを読むときの参考になるのではないかとも思う.
Stackless Pythonの説明はStackless Python入門 - NO!と言えるようになりたいで行ったので,興味があれば参考にして頂きたい.
Pythonのオブジェクトを表す構造体
少し見たところ,Pythonではオブジェクトを表す構造体は,構造体メンバの先頭にPyObject,またはPyVarObjectという構造体を持つようである.たとえば,Pythonで文字列を表すオブジェクトは,str型(Python2.xでいうunicode型)であるが,str型を表す構造体は次のように定義されている.
/* Include/unicodeobject.h */ 437 typedef struct { 438 PyObject_HEAD /* PyObject ob_base; と同じ意味 */ 439 Py_ssize_t length; /* Length of raw Unicode data in buffer */ 440 Py_UNICODE *str; /* Raw Unicode buffer */ 441 long hash; /* Hash value; -1 if not set */ 442 int state; /* != 0 if interned. In this case the two 443 * references from the dictionary to this object 444 * are *not* counted in ob_refcnt. */ 445 PyObject *defenc; /* (Default) Encoded version as Python 446 string, or NULL; this is used for 447 implementing the buffer protocol */ 448 } PyUnicodeObject;
また,list型を表す構造体は以下のようになっている.
/* Include/listobject.h */ 22 typedef struct { 23 PyObject_VAR_HEAD /* PyVarObject ob_base; と同じ意味 */ 24 /* Vector of pointers to list elements. list[0] is ob_item[0], etc. */ 25 PyObject **ob_item; 26 ... /* コメント省略 */ 38 Py_ssize_t allocated; 39 } PyListObject;
PyObject構造体
これらの構造体の先頭には,PyObject_HEAD または,PyObject_VAR_HEAD と宣言されているが,これらはマクロとなっており,以下のように定義されている.
/* Include/object.h */ 80 /* PyObject_HEAD defines the initial segment of every PyObject. */ 81 #define PyObject_HEAD PyObject ob_base; ... /* 省略 */ 90 /* PyObject_VAR_HEAD defines the initial segment of all variable-size 91 * container objects. These end with a declaration of an array with 1 92 * element, but enough space is malloc'ed so that the array actually 93 * has room for ob_size elements. Note that ob_size is an element count, 94 * not necessarily a byte count. 95 */ 96 #define PyObject_VAR_HEAD PyVarObject ob_base;
これを見て分かるように,PyObject_HEAD では PyObject 型の変数を,PyObject_VAR_HEADでは PyVarObject 型の変数を定義しているだけとなる.これだけ見ると,マクロにしなくても良いように思われるが,わざわざマクロとしているのは,おそらく,変数名を固定したいからじゃないかと予想する.
PyObject 構造体と,PyVarObject 構造体は同様に,Include/object.h にて以下のように定義されている.
/* Include/object.h */ 68 /* Define pointers to support a doubly-linked list of all live heap objects. */ 69 #define _PyObject_HEAD_EXTRA \ 70 struct _object *_ob_next; \ 71 struct _object *_ob_prev; ... /* 省略 */ 99 /* Nothing is actually declared to be a PyObject, but every pointer to 100 * a Python object can be cast to a PyObject*. This is inheritance built 101 * by hand. Similarly every pointer to a variable-size Python object can, 102 * in addition, be cast to PyVarObject*. 103 */ 104 typedef struct _object { 105 _PyObject_HEAD_EXTRA 106 Py_ssize_t ob_refcnt; 107 struct _typeobject *ob_type; 108 } PyObject; 109 110 typedef struct { 111 PyObject ob_base; 112 Py_ssize_t ob_size; /* Number of items in variable part */ 113 } PyVarObject;
まず,PyObject 構造体から見てみる.PyObject 構造体の中では,_PyObject_HEAD_EXTRAというマクロが使われており,その後 ob_refcnt と ob_type という変数が定義されいるのが分かる.
_PyObject_HEAD_EXTRA だが,これは上記コードを見て分かるとおり,双方向リンクリストを作成するためのポインタである,_ob_next と _ob_prev を宣言しているに過ぎない.Pythonでは,一端 malloc() で確保したオブジェクト構造体が必要無くなった場合,すぐに free() で開放せずに,一定数をバッファしておき再利用する.これはフリーリストと言う手法であるが,この時に _ob_next と _ob_prev が使われる.フリーリストの詳細については,別の機会に改めて見る予定.
リファレンスカウント
ob_refcnt はリファレンスカウントを保持するための変数となる.ob_refcnt の初期化,加算,減算は,_Py_NewReference,Py_INCREF,Py_DECREF マクロによって行われ,これらマクロは次のように定義されている.
/* Include/object.h */ 663 #define _Py_NewReference(op) ( \ 664 _Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA \ 665 _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \ 666 Py_REFCNT(op) = 1) /* (((PyObject*)(ob))->ob_refcnt) */ ... /* 省略 */ 670 #define _Py_Dealloc(op) ( \ 671 _Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA \ 672 (*Py_TYPE(op)->tp_dealloc)((PyObject *)(op))) 673 #endif /* !Py_TRACE_REFS */ 674 675 #define Py_INCREF(op) ( \ 676 _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \ 677 ((PyObject*)(op))->ob_refcnt++) 678 679 #define Py_DECREF(op) \ 680 do { \ 681 if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \ 682 --((PyObject*)(op))->ob_refcnt != 0) \ 683 _Py_CHECK_REFCNT(op) \ 684 else \ 685 _Py_Dealloc((PyObject *)(op)); \ 686 } while (0)
_Py_NewReference
まず,_Py_NewReference を見てみる.664 行目の_Py_INC_TPALLOCS(op) マクロでは,そのオブジェクト全体が確保された回数の合計を記録するために,Objects/object.c:139 の inc_count() を呼び出しているだけである.おそらくこれは,デバッグなどの為に用いられると予想されるので,説明は割愛する._Py_INC_REFTOTAL もデバッグように使われるマクロであり,リファレンスカウントの合計をカウントしているだけであり,これも説明しない.
_Py_NewReference マクロの中で重要なのは,Py_REFCNT(op) = 1 だけある.Py_REFCNT もマクロであり,その実態は以下のように定義されている.
/* Include/object.h */ 115 #define Py_REFCNT(ob) (((PyObject*)(ob))->ob_refcnt)
要するにただ単に,ob_refcnt = 1 と,リファレンスカウントの値を初期化しているだけである.
Py_INCREF
次は,リファレンスカウントを増やす Py_INCREF を見てみる.やはり,671 行目ではデバッグ用のために _Py_INC_REFTOTAL でリファレンスカウントの合計をカウントしている.その後の 672 行目でリファレンスカウントをインクリメントしているが,ここでは Py_REFCNT マクロは使われていない.奇妙である.
Py_DECREF
最後に,リファレンスカウントを減らす Py_DECREF マクロを見てみる.同じく,681 行目の,_Py_DEC_REFTOTAL はリファレンスカウントの合計をカウントするためのデバッグ用マクロとなる.682 行目でリファレンスカウントがデクリメントされている.やはりここでも,Py_REFCNT マクロは使われていない.
リファレンスカウントがデクリメントされた後,その値が 0 で無かった場合,デバッグ時には _Py_CHECK_REFCNT でリファレンスカウントが正の値であるかどうかのチェックが行われる.この詳細については,割愛する.
逆に,値が 0 であった場合は,_Py_Dealloc が呼ばれて,オブジェクトが開放される._Py_Dealloc の671 行目では,デバッグのために _Py_INC_TPFREES マクロを呼び出して,オブジェクトを開放した回数の合計をカウントしていおり,672 行目で,オブジェクト解放用のコールバック関数を呼び出している.
Py_TYPE マクロは,オブジェクトを表す構造体のポインタから ob_type 変数へ変換しているのみで,その定義は次のようになっている.
/* Include/object.h */ 116 #define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
ob_type は _typeobject 構造体の変数であり,_typeobject 構造体の tp_dealloc が,そのオブジェクト用の関数ポインタとなっている.そのため,オブジェクトの開放時には,tp_dealloc が示す関数が呼ばれることになる.
タイプオブジェクト
PyObject構造体の ob_type 変数は,_typeobject 構造体へのポインタとなっている.Pythonでは,型を表す型も,やはりPythonのオブジェクトであり,_typeobject 構造体もオブジェクトと同じように扱う.その証拠に,_typeobject 型の先頭メンバは PyObject 構造体となっている.
Python では,オブジェクトが生成されるときは,PyObject 構造体から派生した構造体のインスタンスを生成し,そのインスタンスは,自分の型を表す _typeobject 構造体インスタンスへのポインタを持つ._typeobject 構造体には,型名や,解放など各種関数へのポインタなど様々な情報が含まれる._typeobject 構造体の詳細については,また別の機会に述べる予定.
PyVarObject
PyVarObject 構造体であるが,これは,PyObject 構造体に,Py_ssize_t ob_size; というメンバが追加されただけとなる.この ob_size はオブジェクトが持つ要素の数を示す変数となるらしい.PyVarObject は,リストオブジェクトを表す構造体など,複数の要素数を持つオブジェクトに使われているっぽい.詳細についてはまだ追えていないので,理解でき次第おいおい解説したい.