第1篇:CPython實現(xiàn)原理:萬物皆為PyObject

對象的定義

在C/C++中窥突,對象就是堆(Heap)內(nèi)存中的內(nèi)存實體努溃,從簡單的基本數(shù)據(jù)類型(int,float,char)到聚合的數(shù)據(jù)類型(struct)一切皆為對象,我們說基本的數(shù)據(jù)類型是簡單的對象(Simple Object)阻问,因為它僅包含數(shù)據(jù)屬性梧税。而struct級別的數(shù)據(jù)類型是完整的對象(Concrete Object),因為完整的對象具有屬性行為兩個基本概念称近。

  • 屬性就是結(jié)構(gòu)體的數(shù)據(jù)字段第队,可以是基本數(shù)據(jù)類型,指針類型刨秆、甚至是嵌套的struct類型凳谦。
  • 行為又稱為方法成員函數(shù),就是struct內(nèi)部定義的一系列函數(shù)指針。

備注:如有疑問:請移至《C++ 面向?qū)ο蟆?/a>和《C++ 多態(tài)》

Python對象的本源 PyObject

CPython是用C語言實現(xiàn)的衡未,那么用C/C++中關(guān)于對象的概念尸执,去理解Python對象也是理所當然的。先看一下CPython中關(guān)于PyObject的定義

typedef struct _object {
    _PyObject_HEAD_EXTRA  
    Py_ssize_t ob_refcnt;     //引用計數(shù)器缓醋,和內(nèi)存回收有關(guān)
    PyTypeObject *ob_type;  //定義Python對象的元類信息
} PyObject;

其實整個PyObject的難點是就是第三個字段PyTyepObject,也是整個PyObject的核心,包括基本的類型信息:類名稱剔交,類型尺寸(需要分配多大的內(nèi)存)以及類綁定的方法(即綁定的函數(shù)指針)。后文會詳細談到改衩,而_PyObject_HEAD_EXTRA這個宏的定義如下

#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

而以發(fā)布模型的編譯CPython源代碼的話,_PyObject_HEAD_EXTRA這段宏定義是不存在驯镊,因此PyObject的定義可以簡化為

typedef struct _object {
    Py_ssize_t ob_refcnt;     //引用計數(shù)器葫督,和內(nèi)存回收有關(guān)
    PyTypeObject *ob_type;  //定義Python對象的元類信息
} PyObject;

在Python的世界觀中一切皆為PyObject這個話怎么理解呢竭鞍?在Python語義中,每當我們實例化任意一個Python對象橄镜,在其占用的堆內(nèi)存區(qū)塊的首個字節(jié)就包含一個PyObject定義的副本偎快,除PyObject的相關(guān)內(nèi)存字節(jié)副本外,跟隨PyObject對象內(nèi)存副本之后的內(nèi)存字節(jié)是當前對象的內(nèi)存信息洽胶。舉個例子晒夹,比如PyLongObject,繼承PyVarObject,我們先看看位于Include/longintrepr.h定義

struct _longobject {
    PyObject_VAR_HEAD
    digit ob_digit[1];
};

而PyObject_VAR_HEAD這個宏定義實質(zhì)上是PyVarObject類型编振,而PyVarObject本來就繼承PyObject,其定義如下Include/object.h

....
#define PyObject_VAR_HEAD      PyVarObject ob_base;
....
typedef struct {
    PyObject ob_base;
    //這是一個指向C類型的數(shù)據(jù)指針指向的堆內(nèi)存區(qū)域
    //ob_size就是統(tǒng)計該堆內(nèi)存能夠容納元素個數(shù)的計數(shù)器
    Py_ssize_t ob_size; 
} PyVarObject;

任何一個Python對象繼承PyVarObject坐桩,表明它是一個可變長對象(或叫容器對象)未玻,其PyLongObject的完整形式的定義如下

struct _longobject{
    PyObject ob_base;   //PyObject的內(nèi)存副本
    Py_ssize_t ob_size; //數(shù)據(jù)指針的計數(shù)器
    //其實ob_digit是一個堆中的數(shù)組喝检,只不過目前指向索引1的元素
    digit ob_digit[1];        
} PyLongObject;

或者是這樣

struct _longobject{
    Py_ssize_t ob_refcnt;     //引用計數(shù)器累贤,和內(nèi)存回收有關(guān)
    PyTypeObject *ob_type;    //定義Python對象的元類信息
    Py_ssize_t ob_size;       //數(shù)據(jù)指針的計數(shù)器
   //數(shù)據(jù)指針 ob_digit构哺,而ob_digit[1]標識一個digit類型的數(shù)組元素
    digit ob_digit[1];         
} PyLongObject;

好吧底燎!用一個類繼承圖就比較直觀


從內(nèi)存圖的角度來看蠢络,如下圖所示禾唁,PyLongObject類型的對象效览,我們說當實例化一個PyLongObject的實例,它首先要初始化PyVarObject的內(nèi)存中的數(shù)據(jù)(所有字段的默認值)荡短,我們說PyLongObject的實例持有PyVarObject的內(nèi)存副本丐枉。而PyVarObject初始化,也要初始化PyObject的內(nèi)存數(shù)據(jù)掘托。換句話說瘦锹,在Python內(nèi)部,每個堆吸納個都擁有相同的對象頭部烫映,這使對象的引用變得單一化沼本,只需一個PyObject*指針就可以任意引用一個對象


備注:其實C++面向?qū)ο竽P痛篌w上是這么一個套路锭沟,只不過C++運行時增加了一些對象訪問控制設(shè)定抽兆,而C實現(xiàn)的PyObject是不存在所謂訪問控制設(shè)定一回事。

定長對象和變長對象

在CPython 3.x像整數(shù)類型的Python對象族淮,其具體的數(shù)字字面量ob_digit[1](注意ob_digit是一個unsigned int數(shù)組)辫红。例如

>>> n=int(1)
>>> n2=int(9999)
>>> n
1
>>> n2
9999
>>> import sys
>>> sys.getsizeof(n)
28
>>> sys.getsizeof(n2)
28

我們說像int,double這類的基本數(shù)據(jù)類型,他們不同的實例有固定長度的祝辣,我們說這些叫定長對象贴妻。而變長對象即對象的類型尺寸是可變的。例如PyStringObject蝙斜、PyListObject名惩、PyDictObject這些都是可變長對象(也叫容器對象,這和C++標準庫的容器對象非常相似了)孕荠。容器對象的最基本的特征娩鹉。其struct內(nèi)部維護著一個數(shù)據(jù)指針(指向堆中一片連續(xù)的內(nèi)存區(qū)域)攻谁,以及一個計數(shù)器ob_size就是實時統(tǒng)計該堆內(nèi)存區(qū)域有多少個數(shù)據(jù)實體。目前僅需簡單了解這些概念即可弯予。

PyTypeObject

我們說過任意一個PyObject的實例創(chuàng)建過程中戚宦,需要知道類型名,需要分配的堆內(nèi)存锈嫩,以及該對象實例配套的行為(即函數(shù)指針)受楼,這些信息均包含在PyTypeObject實例中,注意我的描述,這個關(guān)鍵字定義必須牢記呼寸。

重要概念: PyTypeObject實例是指在CPython3.x源碼中艳汽,滿足“Py<類型名稱>_Type”這樣的命名風格的結(jié)構(gòu)體初始化代碼都叫PyTypeObject實例。

例如PyLongObject等舔,對應的PyTypeObject實例就是PyLong_Type骚灸,PyListObject對應的PyTypeObject實例是PyList_Type,等等。下文會詳細談到慌植。

我們先查看一下PyTypeObject的類定義甚牲,如下所示,完整代碼見Include/cpython/object.h的第193行-274行蝶柿。

struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; //類型名稱
    //內(nèi)存分配的類型尺寸
    Py_ssize_t tp_basicsize, tp_itemsize; 
    /* Methods to implement standard operations */

    destructor tp_dealloc;
    Py_ssize_t tp_vectorcall_offset;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                    or tp_reserved (Python 3) */
    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;

    //函數(shù)來訪問對象作為I/O緩沖器
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    unsigned long tp_flags;

    const char *tp_doc; /* Documentation string */
    ...
    //屬性描述符和子類相關(guān)信息丈钙,這里定義
    //這里定義了對象的行為
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    Py_ssize_t tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
    destructor tp_del;
    .....
}  PyTypeObject;

在PyTypeObject中有三個非常重要的字段,分別是tp_as_number交汤、tp_as_sequence雏赦、tp_as_mapping。它們分別指向PyNumberMethod芙扎、PySequenceMethods和PyMappingMethods的函數(shù)族星岗。

  • PyNumberMethod:定義了Python對象的行為可以像數(shù)字類型執(zhí)行乘除加減等操作。
  • PySequenceMethods:定義了Python對象作為一個順序表一樣的行為戒洼,例如list俏橘。
  • PyMappingMethods:定義Python對象的關(guān)聯(lián)行為,例如dict

Python對象的類型信息

從PyTypeObject定義中起始字段是一個PyObject_VAR_HEAD,這說明PyTypeObject也是PyObject圈浇,正好說明CPython中寥掐,一切事物都是Python對象,而每個對象有其一個對應的Type磷蜀。注意:**不論什么編程語言召耘,當說一個對象是什么類型,即意味最起碼的三點信息:1.類型名稱、2.類型尺寸褐隆、3.對象內(nèi)存地址污它。

來看一個例子來展示一下什么是PyTypeObject實例

>>> class Student(object):
...     pass
... 
>>> type(Student)
<class 'type'>
>>> st=Student()
>>> type(st)
<class '__main__.Student'>
>>> i=int(10)
>>> type(i)
<class 'int'>
>>> type(int)
<class 'type'>
>>> 

每個用戶定義的Python對象的實例可以由其類內(nèi)部關(guān)聯(lián)的PyTypeObject確定其類型(即type名稱、其對象占用堆內(nèi)存尺寸、對象內(nèi)存地址...)轨蛤,那么PyTypeObject本身也是Python對象蜜宪,其類型是什么呢?就是Type類型祥山,當然PyTypeObject的類型由PyType_Type實例來確定。具體的代碼見詳細代碼請查閱Objects/typeobject.c第3738行-3781行掉伏。

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                      /* tp_name */
    sizeof(PyHeapTypeObject),                    /* tp_basicsize */
    sizeof(PyMemberDef),                         /* tp_itemsize */
    (destructor)type_dealloc,                    /* tp_dealloc */
    offsetof(PyTypeObject, tp_vectorcall),      /* tp_vectorcall_offset */
    ....
    (ternaryfunc)type_call,                     /* tp_call */
    0,                                          /* tp_str */
    (getattrofunc)type_getattro,                /* tp_getattro */
    (setattrofunc)type_setattro,                /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
    Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS |
    Py_TPFLAGS_HAVE_VECTORCALL,                 /* tp_flags */
    type_doc,                                   /* tp_doc */
    (traverseproc)type_traverse,                /* tp_traverse */
    (inquiry)type_clear,                        /* tp_clear */
    0,                                          /* tp_richcompare */
    offsetof(PyTypeObject, tp_weaklist),        /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    type_methods,                               /* tp_methods */
    type_members,                               /* tp_members */
    type_getsets,                               /* tp_getset */
    ....
};

這里需要思考一個問題缝呕,當我們在解釋器接受到類似type(Student)、type(123)這些語句斧散,CPython內(nèi)部如何獲取其類型信息呢供常?我們之前說過任何一個Python對象內(nèi)存頭部保存著PyObject的內(nèi)存副本,更明確地說是引用計數(shù)器PyTypeObject內(nèi)存副本鸡捐。在Python中所有class關(guān)鍵字的類定義都通過一個與其對應的PyTypeObject實例來創(chuàng)建該類型的對象栈暇。比如Python的int類型對應C層面的PyLongObject類,而PyLongObject的實例化由對應的PyLong_Type實例提供類型信息箍镜。如此類推源祈,還有其他常見的Python對象與PyTypeObject實例的對應關(guān)系,如下表所示

在CPython的運行時中色迂,所有內(nèi)置的數(shù)據(jù)類型已經(jīng)在C代碼中預定義了所有對應類型的PyType_Type實例了香缺,并且這些實例僅在運行時初始化一次

這里我們?nèi)匀灰訮yLongObject為例, 當一個我們實例化一個int類型的對象時歇僧,CPython內(nèi)部需要引用對應的PyLong_Type實例內(nèi)的類型信息图张。PyLong_Type實例的初始化語句見具體源代碼,見Objects/longobject.c的第5671行-5712行,從PyLong_Type源碼可知PyVarObject_HEAD_INIT(&PyType_Type诈悍,0)宏定義在編譯時已經(jīng)經(jīng)歷過如下的替換過程祸轮。

我們說的宏表達式PyVarObject_HEAD_INIT(&PyType_Type)最終等價于

{{0,0,1, &PyType_Type },0},

那么PyLong_Type實例的最終代碼形式,如下所示侥钳,

PyTypeObject PyLong_Type = {
    {{0,0,1, &PyType_Type },0},
    "int",                                      /* tp_name */
    offsetof(PyLongObject, ob_digit),           /* tp_basicsize */
    sizeof(digit),                              /* tp_itemsize */
    ....
    long_to_decimal_string,                     /* tp_repr */
    &long_as_number,                            /* tp_as_number */
    ....
    (hashfunc)long_hash,                        /* tp_hash */
    ....
    PyObject_GenericGetAttr,                    /* tp_getattro */
    ....
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
        Py_TPFLAGS_LONG_SUBCLASS,               /* tp_flags */
    long_doc,                                   /* tp_doc */
    ....
    long_richcompare,                           /* tp_richcompare */
    ....
    long_methods,                               /* tp_methods */
    ....
    long_getset,                                /* tp_getset */
    ....
    long_new,                                   /* tp_new */
    PyObject_Del,                               /* tp_free */
};

如果适袜,你上面代碼還沒有概念的話,我們通過一個實例來說明一些基本的事實慕趴,首先我們在Python解釋器交互命令行創(chuàng)建一個比較無聊的num函數(shù)痪蝇,該函數(shù)旨在其內(nèi)部實例化一個PyLongObject的實例m

可以用gdb工具執(zhí)行Python解釋器,我們通過對Python/ceval.c源碼關(guān)于LOAD_CONST指令定義的C代碼的執(zhí)行斷點冕房,

  • 使用print *value命令,可以知道初始化一個PyLongObject實例時躏啰,它的ob_type字段是指向PyLong_Type實例的內(nèi)存地址。
  • 通過print一下*(value->ob_type)耙册,我們得到PyLong_Type實例的所有具體細節(jié)给僵,該PyLong_Type實例的ob_type字段指向一個PyType_Type實例的內(nèi)存地址,并且tp_base字段是指向一個PyBaseObject_Type的實例。


藥不要停,如此類推我們通過print打印ob_base找到當前PyType_Type子類實例的本體帝际,通過ob_type找到上一級實例的

顯然蔓同,根據(jù)PyVarObject_HEAD_INIT(&PyType_Type,0)可知蹲诀,在PyLongObject實例化過程中,會引用PyLong_Type實例斑粱,而PyLong_Type實例也會引用PyType_Type實例。而PyType_Type實例是整個CPython類型系統(tǒng)的根脯爪,也就是說PyType_Type是所有Python對象的元類则北。我們可以通過一個內(nèi)存圖來得到一個清晰的輪廓

咦,這不就是類似C++類繼承的原理嗎?不用猜痕慢。就是尚揣,同時這也是體現(xiàn)CPython面向?qū)ο竽J街械牧硪粋€特征--多態(tài)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掖举,一起剝皮案震驚了整個濱河市快骗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌塔次,老刑警劉巖方篮,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異俺叭,居然都是意外死亡恭取,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門熄守,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜈垮,“玉大人,你說我怎么就攤上這事裕照≡芊ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵晋南,是天一觀的道長惠猿。 經(jīng)常有香客問我,道長负间,這世上最難降的妖魔是什么偶妖? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮政溃,結(jié)果婚禮上趾访,老公的妹妹穿的比我還像新娘。我一直安慰自己董虱,他們只是感情好扼鞋,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布申鱼。 她就那樣靜靜地躺著,像睡著了一般云头。 火紅的嫁衣襯著肌膚如雪捐友。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天溃槐,我揣著相機與錄音匣砖,去河邊找鬼。 笑死昏滴,一個胖子當著我的面吹牛脆粥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播影涉,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼规伐!你這毒婦竟也來了蟹倾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤猖闪,失蹤者是張志新(化名)和其女友劉穎鲜棠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體培慌,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡豁陆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吵护。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盒音。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖馅而,靈堂內(nèi)的尸體忽然破棺而出祥诽,到底是詐尸還是另有隱情,我是刑警寧澤瓮恭,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布雄坪,位于F島的核電站,受9級特大地震影響屯蹦,放射性物質(zhì)發(fā)生泄漏维哈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一登澜、第九天 我趴在偏房一處隱蔽的房頂上張望阔挠。 院中可真熱鬧,春花似錦帖渠、人聲如沸谒亦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽份招。三九已至切揭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锁摔,已是汗流浹背廓旬。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谐腰,地道東北人孕豹。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像十气,于是被迫代替她去往敵國和親励背。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359