PyObject對象機制的基石
學過Python的人應該非常清晰,Python中一切都是對象,全部的對象都有一個共同的基類,對於本篇博文來說,一切皆是對象則是探索Python的對象機制的一個入口點.我如果讀者在閱讀本文的時候已經下載Python(Python-2.7.11)的源代碼,而且已經解壓進入了源代碼的根文件夾下.眾所周知Python是用C實現的,C是一種OO的語言。而Python是一個OOP的語言,那么怎樣在C語言層面實現OOP,實現多態,這是一個有意思的話題,這也是本文須要進行探索的點.Python內部使用了一個PyObject
結構體來保存全部對象共同的數據成員,以及實現GC機制所須要的一些輔助字段等.所以能夠說PyObject
就是Python對象機制的基石。這毫不為過.那么讓我們進入到源代碼中.透過源代碼看看Python中的對象究竟是個啥?
PyObject對象
typedef struct _object {
PyObject_HEAD
} PyObject;
./Include/object.h
異常的簡單,這一切都要歸功於PyObject_HEAD
宏,C語言中的宏是把雙刃劍.看看PyObject_HEAD
長什么樣吧
#define PyObject_HEAD \
_PyObject_HEAD_EXTRA \
Py_ssize_t ob_refcnt; \
struct _typeobject *ob_type;
./Include/object.h
初看起來。還是不easy理解的,只是經驗告訴我_PyObject_HEAD_EXTRA
這個宏能夠不用管。由於這是以下划線開頭的,這類變量一般都是內部使用.為此我找到這個宏驗證了我的想法.
/* Py_DEBUG implies Py_TRACE_REFS. */
#if defined(Py_DEBUG) && !defined(Py_TRACE_REFS)
#define Py_TRACE_REFS
#endif
/* Py_TRACE_REFS implies Py_REF_DEBUG. */
#if defined(Py_TRACE_REFS) && !defined(Py_REF_DEBUG)
#define Py_REF_DEBUG
#endif
//注意看這里,僅僅有定義了Py_TRACE_REFS這個宏_PyObject_HEAD_EXTRA才不是空.
//而Py_TRACE_REFS這個宏僅僅有在Py_DEBUG有定義的情況下才會定義,可想而知 _PyObject_HEAD_EXTRA僅僅有在DEBUG模式
//才有意義,否則就是一個空.
#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
#define _PyObject_EXTRA_INIT 0, 0,
#else
#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT
#endif
./Include/object.h
_PyObject_HEAD_EXTRA
我們能夠略過不看了,這個宏的作用僅僅是在DEBUG模式下,將全部的對象使用雙鏈表鏈起來方便DEBUG而已.去掉_PyObject_HEAD_EXTRA
后再來看看PyObject
長啥樣吧.
typedef struct _object {
Py_ssize_t ob_refcnt; \
struct _typeobject *ob_type;
} PyObject;
./Include/object.h
異常的清晰是不是,Py_ssize_t
僅僅只是是對C語言的基本類型的一個typedef而已,這里能夠覺得就是ssize_t類型了,ob_refcnt
指的就是上文中說的GC機制須要的輔助字段用於維護當前對象的引用個數,ob_type
是個啥呢?,這個東西說簡單點就是指明當前對象是何種類型.后面會單獨拿出來具體的介紹,接下來看下怎樣使用PyObject
來搭建整個Python的對象機制,以下是int對象的底層表示:
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject
./Include/intobject.h
包括了PyObject
,除此之外包括了一些這些對象特有的一些數據成員,比方這里的ob_ival
。就是用來保存int對象的具體數值的.那么String對象可想而知應該有一個char*
的指針指向一段heap的內存空間,還須要有一個數據成員來保存當前的String的字符個數,那么list對象呢?,dict對象呢?,細想一下這些對象都應該包括一個指明當前對象包括的元素的個數的數據成員,好吧,這里我們找到了一些共同點。到這里產生了一個技術問題的抉擇了,int對象不須要有指明當前對象包括元素個數的數據成員,而string對象,list對象,dict對象都須要有,那么這個數據成員應該放在PyObject
中,還是歸為每一個對象自己特有的數據成員呢?,Python選擇了前者將這個數據成員放到了PyObject
中。但又沒全然這樣做.這一切都歸功於Python中的另外一個概念,可變對象和不可變對象.以下通過源代碼我們來看看Python究竟是怎么做的
PyVarObject對象
typedef struct {
PyObject_VAR_HEAD
} PyVarObject;
./Include/object.h
弄了一個PyVarObject
對象出來了,事實上這是一個所謂的紙老虎。打開PyObject_VAR_HEAD
這個宏你就會發現真相原來是這樣.
#define PyObject_VAR_HEAD \
PyObject_HEAD \
Py_ssize_t ob_size; /* Number of items in variable part */
./Include/object.h
又是異常的簡單。就是在原有的PyObject
基礎上加了一個ob_siz
e成員,用來指明當前對象所擁有的元素個數.可是有一個須要注意就是ob_size
數據成員的位置僅僅能在PyObject_HEAD
以下,目的是為了能夠通過PyObject
強制轉換成不論什么一個對象.如果你不明確,那么來張圖看看.
回到Python
源代碼看完了,是不是有點想大展宏圖的沖動。可惜依照我們眼下的水平。相對Python做些什么恐怕還不夠格。只是我們能夠利用Python來驗證下我們讀源代碼的收獲.通過上文分析的PyObject
,還有PyIntObject
。我們來計算一下一個PyIntObject
大小。
typedef struct {
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
long ob_ival;
} PyIntObject
一個ob_refcnt
這是一個ssize_t
類型,64為系統下是8個字節,一個ob_type
是個指針,在64位系統下相同也是8個字節,一個long類型在64位系統下相同也是8個字節所以算起來一個PyIntObject
是24個字節,回到Python中我們來驗證一下:
>>> import sys
>>> inttype = 1
>>> sys.getsizeof(inttype)
24