Python 對象底層實現分析

PyObject

PyObject對象是一切python對象共有的部分,包含以下內容:

typedef struct _object{

    int refcnt;  // 用於保存一個對象的引用計數,當一個對象引用減爲0時,將會對這個對象進行一定處理(不一定就會直接釋放內存資源)

    struct _typeobject *ob_type;  // 指向這個對象對應類型的類型對象(類型是由這些類型對象創建的),這些類型對象中存放了這種類型的對象可以進行的各種操作(比強加減),還有類型的相關信息等

} PyObject;

PyObject是所有對象共有的頭部,所以可以通過PyObject *來引用任意一個對象, 類似於C++的繼承。

PyVarObject

Python中除了有對象可分爲兩種:定長對象(int, float ...),不定長對象(字符串等);

那麼Python中除了PyObject外還有一個專門用於表示變長對象的結構體 --- PyVarObject

typedef struct{

    int refcnt;

    struct _typeobject *ob_type;

    int ob_size;  // 除了上面兩個共有的屬性外,ob_size用於存放變長對象容納的元素的個數(!= 字節數)

} PyVarObject;

PyTypeObject

用於創建類型對象

比如,整數2這個對象是由PyIntObject創建的,但是int這個類型(也是個對象)是由誰創建的呢?答案就是 PyTypeObject

typedef struct _typeobject {
    PyObject_VAR_HEAD  // 包含上面PyVarObject中的三個成員
    const char *tp_name;  // 類型名(int, ....)
    // 下面很多是有關相關類型對象支持的操作等信息

    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    cmpfunc tp_compare;
    reprfunc tp_repr;

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    ........

} PyTypeObject;

每個類型都有相應的類型對象。類型對象相當於一箇中轉站,比如輸出一個對象的值

先獲取了ob_type也就是類型對象,然後調用相應的tp_print(不同類型數據他們的tp_print域是不一樣的)。這樣在操作一個對象時,不需要先把這個對象轉化爲相應的對象,而僅用PyObject這一塊區域(包括類型對象)就可以了,因爲該對象的操作定義在類型對象中,比如現在我們只知道一個PyObject *a, 要輸出a的值,只需要a->ob_type->tp_print(...), 而不是要先知道a的類型(整型,字符串), 轉換完(PyIntObject*)a, 然後再取出值轉換完(PyIntObject*)a->val;

這樣的好處就是在各函數間傳遞時,我們可以只用PyObject*這種泛型指針即可完成對該對象的操作。

PyType_Type

上面說了類型也是一個對象,但怎麼知道這個對象是一個類型對象呢?---PyType_Type

也就是說對於普通對象,通過其對應的類型對象來確定其類型,

通過PyType_Type來確定一個對象是否是類型對象。

// PyType_Type也是由PyTypeObject創建的
PyTypeObject PyType_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,                    /* ob_size */
    "type",                    /* tp_name */
    sizeof(PyHeapTypeObject),        /* tp_basicsize */
    sizeof(PyMemberDef),            /* tp_itemsize */
    (destructor)type_dealloc,        /* tp_dealloc */
    0,                    /* tp_print */
    0,                     /* tp_getattr */
    0,                    /* tp_setattr */
    type_compare,                /* tp_compare */

    .........

}

所有用戶自定義的class都是由這個PyType_Type來創建的,也就是Python中的metaclass。

 

·所有類型對象(int類型的類型對象,字符串類型的類型對象....)不會被析構, 上面說了相應的對象是由類型對象來創建的,要是類型對象都沒了,誰來創建這些整數,浮點數,字符串對象呢。

 

PyIntObject

Python中的整數對象(不可變,定長)

對應的類型對象----PyInt_Type, 裏邊定義了整數對象的相關信息以及可以支持的各種操作。

typedef struct {
    int refcnt;

    struct _typeoject *ob_type;
    long ob_ival;   // 用於存放整數對象的值,上面是共有的頭部部分
} PyIntObject;

 

PyStringObject

字符串對象, 定義如下

typedef struct {

    PyObject_VAR_HEAD;  // 變長對象共有頭部,上面已經介紹過了。

    long ob_shash;  // 採用內部某種算法來計算該字符串的hash值,緩存到這裏,避免每次都計算

    int ob_sstate;  // 標記該字符串是否經過intern機制處理,下面介紹

    char ob_sval[1];  // 指向字符串內容

} PyStringObject;

intern機制:可以節省內存空間及提高虛擬機運行的效率。

比如:a="Python"    b="Python", 那麼不採取任何措施的話,內部就要維護兩份相同的字符串對象了,如果是一百個"Python"呢, 那豈不是要100個相同的對象?

爲了處理這種情況,Python提供了intern機制,比如對於上面b採用了inter機制後,那麼創建b的時候,會先在intered這個字典中查找是否已經存在也經過inter機制處理的相同字符串,如果找到了那麼就銷燬b指向的字符串對象,轉而讓b指向查找出的相同內容的字符串對象(a創建的"Python"對象),這時可以看到內存中只有一個"Python"字符串對象,ab都指向它,引用計數爲2.

上面就是intern操作的代碼,interned是一個字典,用來存放已經經過intern操作的字符串。

注意上面在進行intern處理之前a指向的"Python",與b指向的"Python"對象是不同的。

PyDIct_GetItem(intered, s);  查找interned字典中是否存在,如果存在,那麼把t的引用計數加一,*p的引用計數減一(這裏的t就相當於上面a指向的"Python"對象,*p就是b指向的這個臨時的"Python"對象),相當於把這個臨時對象“銷燬“了(當然只是引用計數減一變爲0了)。如果沒找到,就到下面的PyDict_SetItem(), 把新創建的這個對象(b指向的"Python"對象)添加到interned中。

PyListObject:

  Python的列表有一個很大的好處就是可以把任意類型的數據放進去,是怎麼實現的呢?其實原因就在文章的開頭。

  Python中所有的對象都可以用PyObject*來指向,PyIntObject, PyStringObject, 包括這裏的PyListObject都可以用PyObject*類型的指針來指向,因爲他們的頭部都是一樣的PyObject, 其實List中並不是直接存放各類型的指針,而是存放的PyObject*指針。下面來看一下定義:

PyObject_VAR_HEAD: 變長對象共有頭部,不解釋了。

allcated: 申請的總內存大小(可能有申請了但沒使用的)

ob_item: 這裏就是上面說的存放PyObject指針的地方, 不太習慣的話,換一種形式PyObject **ob_item換成PyObject* ob_item[], 這樣就很明顯了,ob_item這個數組裏放的都是PyObject的指針,這麼說其實不是很合適,雖然是PyObject*,但實際指向的可能是PyIntObject, PyStringObject....

可能又有疑問了,既然都是PyObject*, 那怎麼知道指向的是整數對象還是字符串對象呢,其實上面也已經解釋了(PyObject區域中有類型對象,而類型對象中存放了該種類型數據的操作)。

 

PyDictObject:

 

搜索算法是散列表,這個對象不再說了,太麻煩,這個對象對效率要求很高,因爲和Python虛擬機直接相關的,比如名字空間,還有上面的interned等都是直接使用了這種數據結構。

 

參考:《Python源碼剖析》

如有錯漏,懇請指正。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章