python底层源码_python源码剖析——系列一
0 前言
Python 非常好用,哪怕一個(gè)沒(méi)上過(guò)匯編,操作系統(tǒng),編譯原理等一系列基礎(chǔ)計(jì)算機(jī)課程的人,也能快速上手。
再拿反面教材C++舉例,引用和指針的區(qū)別入門階段就搞懵了一批人。而指針和引用如果拓展開(kāi),C++老司機(jī)也是很容易翻車的。
Python好用的一個(gè)原因,就是把底層的很多復(fù)雜內(nèi)容給封裝簡(jiǎn)化了,當(dāng)然很多動(dòng)態(tài)語(yǔ)言也都再這么干(如PHP),只不過(guò)Python的用戶體驗(yàn)大家一致覺(jué)得更好。
這個(gè)筆記系列,想從源碼的角度來(lái)看,Python是如何把底層復(fù)雜內(nèi)容進(jìn)行封裝的。
第一篇主要先講大致框架,再拿int類型做一些展開(kāi)。基于Python2.7的源碼,Python3.0的源碼會(huì)有區(qū)別,這個(gè)要注意。
1 萬(wàn)物皆對(duì)象,對(duì)象也為對(duì)象
先舉個(gè)例子
Def test(variable):
Print type(variable)
Python中variable可以為任何東西,int, dict, list,string,function。
對(duì)小白來(lái)講,寫(xiě)函數(shù)不用考慮變量類型,學(xué)習(xí)和使用體驗(yàn)是很好的。(當(dāng)然,在大型項(xiàng)目重構(gòu)的時(shí)候,發(fā)現(xiàn)函數(shù)無(wú)法確定變量類型,返回類型,是很蛋疼的事情。所謂,動(dòng)態(tài)一時(shí)爽,全家火葬場(chǎng))
這種操作,C++中叫多態(tài),而多態(tài)必須有一個(gè)共同的父親節(jié)點(diǎn)。同理,Python底層C實(shí)現(xiàn)也是多態(tài),都有一個(gè)共同的父類。
也就是,萬(wàn)物皆對(duì)象,對(duì)象也為對(duì)象。
1.1 背景知識(shí)——C中的多態(tài)實(shí)現(xiàn)方式
typedef struct {
data member_x;
} base;
typedef struct {
struct base;
data member_y;
} derived;
void function_on_base(struct base * a); // here I can pass both pointers to derived and to basevoid function_on_derived(struct derived * b); // here I must pass a pointer to the derived class
實(shí)現(xiàn)思路其實(shí)很簡(jiǎn)單,base class必須要是一個(gè)struct,繼承類必須要在一開(kāi)始就包含base struct。
1.2 對(duì)象三要素
對(duì)象三要素,引用計(jì)數(shù),類型信息,類型內(nèi)容。
這里從先從父親節(jié)點(diǎn)說(shuō)起,PyObject定義如下
[object.h]
/* Nothing is actually declared to be a PyObject, but every pointer to* a Python object can be cast to a PyObject*. This is inheritance built* by hand. Similarly every pointer to a variable-size Python object can,* in addition, be cast to PyVarObject*.*/
typedef struct _object {
PyObject_HEAD
} PyObject;
#define PyObject_HEAD \_PyObject_HEAD_EXTRA \Py_ssize_t ob_refcnt; \struct _typeobject *ob_type;
注釋已經(jīng)說(shuō)得很清楚,Nothing is actually declared to be a PyObject, but every pointer to a Python object can be cast to a PyObject*. This is inheritance built。
而根據(jù)上文多態(tài)的定義,子類在一開(kāi)始包含PyObject_HEAD即可繼承PyObject對(duì)象。
1.2.1 引用計(jì)數(shù)——Py_ssize_t ob_refcnt
內(nèi)存回收機(jī)制中的核心變量,引用計(jì)數(shù),細(xì)節(jié)不展開(kāi)。
1.2.2 類型對(duì)象——struct _typeobject *ob_type
Python中萬(wàn)物皆對(duì)象有多徹底呢?用來(lái)指定一個(gè)對(duì)象類型的類型變量也是一個(gè)對(duì)象。
[object.h]
typedef int (*printfunc)(PyObject *, FILE *, int);
typedef struct _typeobject {
PyObject_VAR_HEAD //根據(jù)宏定義,PyObject_VAR_HEAD即為PyObject const char *tp_name; /* For printing, in format "." */
printfunc tp_print;
//還有幾十個(gè)函數(shù)指針,省略} PyTypeObject;
typedef struct {
PyObject_VAR_HEAD
} PyVarObject;
#define PyObject_VAR_HEAD \PyObject_HEAD \Py_ssize_t ob_size;/* Number of items in variable part */#define Py_INVALID_SIZE (Py_ssize_t)-1
PyTypeObject就是類型對(duì)象,繼承了PyObject。
這個(gè)對(duì)象通過(guò)大量的函數(shù)指針和多態(tài)來(lái)定義了python對(duì)象所應(yīng)該具有的內(nèi)容。
1.2.3 類型內(nèi)容
PyObject做為父類肯定沒(méi)有類型內(nèi)容,但子類,例如int子類,int內(nèi)容放那呢?
[intobject.h]
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
很明顯,在PyObject_HEAD后,加上了long變量來(lái)存儲(chǔ)整數(shù)內(nèi)容。
同理,list,dict,string也是如此設(shè)計(jì),當(dāng)然變長(zhǎng)對(duì)象的設(shè)計(jì)會(huì)更復(fù)雜。
1.3 Python對(duì)象的多態(tài)
類型對(duì)象PyTypeObject通過(guò)函數(shù)指針加多態(tài)來(lái)實(shí)現(xiàn),這里拿printfunc來(lái)舉例。
[object.h]
typedef int (*printfunc)(PyObject *, FILE *, int);
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "." */
printfunc tp_print; //這里定義接口 //還有幾十個(gè)函數(shù)指針,省略} PyTypeObject;
[intobject.c]
PyTypeObject PyInt_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"int",
(printfunc)int_print, /* tp_print */
int_print(PyIntObject *v, FILE *fp, int flags) //在這里實(shí)現(xiàn)接口 /* flags -- not used but required by interface */
{
long int_val = v->ob_ival;
Py_BEGIN_ALLOW_THREADS
fprintf(fp, "%ld", int_val);
Py_END_ALLOW_THREADS
return 0;
}
PyTypeObject 定義printfunc的接口,因?yàn)镻yIntObject是PyObject的子類,所以可以在intobject中實(shí)現(xiàn)這個(gè)接口。換成string,dict,set等對(duì)象實(shí)現(xiàn)原理也一樣。
通過(guò)這三要素,PyObject已經(jīng)把對(duì)象框架搭完畢。如果我們要實(shí)現(xiàn)一個(gè)int對(duì)象,根據(jù)PyObject中的類型中定義的接口,選擇我們所需來(lái)實(shí)現(xiàn)即可。
2 int型對(duì)象分析
int對(duì)象的接口實(shí)現(xiàn)想對(duì)簡(jiǎn)單,但也是有不少有意思的點(diǎn)。
2.1 int對(duì)類型對(duì)象的接口實(shí)現(xiàn)
[intobject.c]
PyTypeObject PyInt_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"int",
sizeof(PyIntObject),
0,
(destructor)int_dealloc, /* tp_dealloc */
(printfunc)int_print, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
(cmpfunc)int_compare, /* tp_compare */
(reprfunc)int_to_decimal_string, /* tp_repr */
&int_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
(hashfunc)int_hash, /* tp_hash */
0, /* tp_call */
(reprfunc)int_to_decimal_string, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_INT_SUBCLASS, /* tp_flags */
int_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
int_methods, /* tp_methods */
0, /* tp_members */
int_getset, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
int_new, /* tp_new */
};
可以看到,并不是類型對(duì)象所有定義的接口,int對(duì)象都需要實(shí)現(xiàn),賦值為0即代表不用實(shí)現(xiàn)。
上文已拿int_print講過(guò)了,更多代碼細(xì)節(jié)建議去看源碼。
2.2 整數(shù)內(nèi)存池
對(duì)C來(lái)講,棧的內(nèi)存申請(qǐng)和銷毀速度要比堆快的多,為什么就不展開(kāi)了。
C中的int,bool等build-in變量都是在棧上操作。Python中萬(wàn)物皆對(duì)象,也就是struct,新建的int對(duì)象要通過(guò)malloc在堆上申請(qǐng)。
這樣速度必然要比C慢一大截,并且日常代碼中,整數(shù)類型的使用是非常頻繁的。
所以,Python就引入內(nèi)存池和內(nèi)存塊來(lái)進(jìn)行加速。
2.2.1 小整數(shù)對(duì)象內(nèi)存池
PyIntObject是不可變對(duì)象,所以可以提前申請(qǐng)內(nèi)存池來(lái)存儲(chǔ)常用的小數(shù)字,直接從內(nèi)存池來(lái)拿就可以使用。
問(wèn)題是,多小的整數(shù)算小整數(shù)呢?Python是可以自定義的。
[intobject.c]
/* References to small integers are saved in this array so that theycan be shared.The integers that are saved are those in the range-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).*/
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
代碼如上,注釋也說(shuō)的比較清晰。
2.2.2 大整數(shù)對(duì)象內(nèi)存塊
小整數(shù)對(duì)象通過(guò)固定的內(nèi)存池解決了內(nèi)存重復(fù)申請(qǐng)的問(wèn)題。大整數(shù)對(duì)象是Python申請(qǐng)了一塊固定的內(nèi)存塊,這些內(nèi)存塊由大整數(shù)輪流使用。
核心是兩個(gè)鏈表指針,分成四步走
[intobject.c]
struct _intblock {
struct _intblock *next;
PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;
static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;
第一步,整數(shù)如果為小整數(shù),則直接從小整數(shù)內(nèi)存池中取。
第二步,free_list如果不為null,則把free_list指向的空余內(nèi)存分配給當(dāng)前大數(shù)。
第三步,free_list如果為null,則申請(qǐng)一個(gè)PyIntBlock對(duì)象,一個(gè)PyIntBlock可以存多少個(gè)int對(duì)抗,量級(jí)可以自定義。
第四步,新申請(qǐng)的內(nèi)存空間,用free_list串起來(lái)即可。具體參看intobject.c中的fill_free_list函數(shù)
還有兩個(gè)關(guān)鍵步驟。
第一,Python是引用計(jì)數(shù)來(lái)釋放內(nèi)存,int類型內(nèi)存釋放后,free_list也要繼續(xù)把這些free的內(nèi)存串聯(lián)起來(lái)。
第二,假如某個(gè)階段int類型申請(qǐng)?zhí)貏e多,PyIntBlock自然也就申請(qǐng)了很多。然后某個(gè)階段int被集中銷毀,那么多個(gè)PyIntBlock是否完全保留,全都用free_list串起來(lái)?還是銷毀大部分,只保留小部分?這塊代碼沒(méi)細(xì)看。
這樣的好處?
核心就一個(gè),減少堆的碎片化。碎片化的壞處這里就不展開(kāi),Java中專門針對(duì)這個(gè)問(wèn)題其實(shí)做了不少優(yōu)化。
舉個(gè)例子,C++的hash有一個(gè)內(nèi)存碎片的問(wèn)題,因?yàn)槊總€(gè)hash值指向的list都是用鏈表,鏈表的內(nèi)存是分散的。對(duì)于超大的hash存儲(chǔ)來(lái)講,會(huì)導(dǎo)致堆的碎片化問(wèn)題。
有一個(gè)優(yōu)化的方式,就是讓hash值指向的是個(gè)偽鏈表,實(shí)際上是個(gè)連續(xù)型內(nèi)存。這個(gè)操作是不是看著跟上文的介紹有一點(diǎn)類似?
3 絮絮叨叨
這篇文章是基于三年前讀《Python源碼剖析》記的筆記,但一直沒(méi)有完整的整理出來(lái)。
最近換工作,有了空閑時(shí)間,就花了幾天時(shí)間整理了一下,還是挺有意思的。
畢竟身為策略工程師,天天用Python還是挺多的,對(duì)底層有一定了解還是挺好的。
后續(xù)的筆記自然就是繼續(xù)把string,list,dict,再到虛擬機(jī)給寫(xiě)寫(xiě),但抽空吧,可能又是三年后了呢。
總結(jié)
以上是生活随笔為你收集整理的python底层源码_python源码剖析——系列一的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java的input不能更改,无法将方法
- 下一篇: java 静态线程_Java线程类静态本