當前位置:
首頁 > 知識 > Python 源碼閱讀:int

Python 源碼閱讀:int

(點擊

上方藍字

,快速關注我們)




來源:伯樂在線 - wklken


如有好文章投稿,請點擊 → 這裡了解詳情




代碼我也僅僅是粗粗讀了一遍, 可能出現疏漏和理解錯誤, 發現瞭望指出哈.




示例




>>>

a

=

1


>>>

b

=

1


>>>

id

(

a

)

==

id

(

b

)


True


 


>>>

c

=

257


>>>

d

=

257

>>>

id

(

c

)

==

id

(

d

)


False


 


#在python2.x中, 對於大的序列生成, 建議使用xrange(100000) 而不是range(100000), why?



源碼位置 Include/intobject.h |


Objects/intobject.c






PyIntObject





typedef

struct

{


    

PyObject_HEAD


    

long

ob_ival

;


}

PyIntObject

;




結構









幾個構造方法





# 從字元串, 生成PyIntObject對象


PyAPI_FUNC

(

PyObject *

)

PyInt_FromString

(

char

*

,

char

**

,

int

);


 


# 從Py_UNICODE, 生成PyIntObject對象


#ifdef Py_USING_UNICODE


PyAPI_FUNC

(

PyObject *

)

PyInt_FromUnicode

(

Py_UNICODE*

,

Py_ssize_t

,

int

);


#endif


 


# 從long值, 生成PyIntObject對象


PyAPI_FUNC

(

PyObject *

)

PyInt_FromLong

(

long

);


 


PyAPI_FUNC

(

PyObject *

)

PyInt_FromSize_t

(

size_t

);


PyAPI_FUNC

(

PyObject *

)

PyInt_FromSsize_t

(

Py_ssize_t

);




這幾個方法, 只需要關注





# 因為大家最後都調用這個方法完成對象生成


PyAPI_FUNC

(

PyObject *

)

PyInt_FromLong

(

long

);






具體的構造方法 PyInt_FromLong




這個方法的定義





PyObject *


PyInt_FromLong

(

long

ival

)


{


    

register PyIntObject *

v

;


 


    

/* MARK: 如果, 值在小整數範圍內, 直接從小整數對象池獲取得到對象 */


 


    

#if NSMALLNEGINTS + NSMALLPOSINTS > 0


    

if

(

-

NSMALLNEGINTS  

ival

&

ival  

NSMALLPOSINTS

)

{


 


        

/* MARK: small_ints是什麼後面說 */


        

v

=

small_ints

[

ival

+

NSMALLNEGINTS

];


        

// 引用+1


        

Py_INCREF

(

v

);


 


        

/* 這裡先忽略, 計數 */


        

#ifdef COUNT_ALLOCS


            

if

(

ival

>=

0

)


                

quick_int_allocs

++

;


            

else


                

quick_neg_int_allocs

++

;


        

#endif


 


        

// 返回


        

return

(

PyObject *

)

v

;


    

}


    

#endif


 


    

// 如果free_list還不存在, 或者滿了


    

if

(

free_list

==

NULL

)

{


        

// 新建一塊PyIntBlock, 並將空閑空間鏈表頭部地址給free_list


        

if

((

free_list

=

fill_free_list

())

==

NULL

)


            

// 如果失敗, 返回


            

return

NULL

;


    

}


 


    

// 從free_list分出一個位置存放新的整數


 


    

/* Inline PyObject_New */


    

// 使用單向鏈表頭位置


    

v

=

free_list

;


 


    

// free_list指向單向鏈表下一個位置


    

free_list

=

(

PyIntObject *

)

Py_TYPE

(

v

);


 


    

// 初始化對象, 類型為PyInt_type, 值為ival


    

PyObject_INIT

(

v

,

&

PyInt_Type

);


    

v

->

ob_ival

=

ival

;


 


    

// 返回


    

return

(

PyObject *

)

v

;


}




注意這裡的Py_TYPE()方法, 在我們第一篇文章裡面有提到, 不知道的回去複習下對象的數據結構





#define Py_TYPE(ob)             (((PyObject*)(ob))->ob_type)




簡而言之:





1.

先判斷數值是否是小整數

,

是的話從小整數對象池裡面直接返回


(

這個池固定大小

,

下一點講

)


 


2.

如果不是

,

從通用整數對象池裡面取一個

,

初始化返回


(

如果這時候通用整數對象池還不存在或者已經滿了

,

新建一個池加入維護

.

通用整數對象池後面講

)






小整數對象池




先看定義





#ifndef NSMALLPOSINTS


#define NSMALLPOSINTS           257


#endif


 


#ifndef NSMALLNEGINTS


#define NSMALLNEGINTS           5


#endif


 


#if NSMALLNEGINTS + NSMALLPOSINTS > 0


/* References to small integers are saved in this array


   so that they can be shared.


   The integers that are saved are those in the range


   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).


*/


 


static

PyIntObject *

small_ints

[

NSMALLNEGINTS

+

NSMALLPOSINTS

];


#endif




其實, 小整數對象池就是一個PyIntObject指針數組(注意是指針數組), 大小=257+5=262, 範圍是[-5, 257) 注意左閉右開. 即這個數組包含了262個指向PyIntObject的指針.




結構





創建整數時, 如果在[-5, 257)範圍, 直接返回已經存在的整數對象指針, 所以我們看到開頭的例子, id比較一個true/一個false




小整數對象池, 在一開始就初始化了, 其初始化代碼





int


_PyInt_Init

(

void

)


{


    

PyIntObject *

v

;


    

int

ival

;


 


    

// 注意這裡, free_list再次出現


 


#if NSMALLNEGINTS + NSMALLPOSINTS > 0


 


    

// 循環, 逐一生成


    

for

(

ival

= -

NSMALLNEGINTS

;

ival

ob_ival

=

ival

;


 


        

// 放到數組裡


        

small_ints

[

ival

+

NSMALLNEGINTS

]

=

v

;


    

}


#endif


 


    

return

1

;


}




代碼很眼熟吧, 覺得不眼熟回上面看代碼




結論





1.

小整數對象池緩存

[

-

5

,

257

)

內的整數對象

,

數值在這個範圍的整數對象有且只存在一個

...


 


2.

小整數對象池

,

只是一個指針數組

,

其真正對象依賴通用整數對象池




通用整數對象池1 – 基礎結構PyIntBlock




首先, 有個數據結構PyIntBlock





#define BLOCK_SIZE      1000    /* 1K less typical malloc overhead */


#define BHEAD_SIZE      8       /* Enough for a 64-bit pointer */


#define N_INTOBJECTS    ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))


 


struct

_intblock

{


    

struct

_intblock *

next

;


    

PyIntObject

objects

[

N_INTOBJECTS

];


};


 


typedef

struct

_intblock

PyIntBlock

;




回憶一下PyIntObject結構(1個int, 1指針, 1個long), size=4+4+4(先這麼算), N_INTOBJECTS = 82




結構







通用整數對象池2 – 創建過程及運行時結構




有兩個指針





# 指向一個block


static

PyIntBlock *

block_list

=

NULL

;


 


# 指向一個PyIntObject


static

PyIntObject *

free_list

=

NULL

;




生成過程的定義





// 初始化一個PyIntBlock


static

PyIntObject *


fill_free_list

(

void

)


{


    

PyIntObject *

p

,

*

q

;


    

// 建立一個新的block


    

/* Python"s object allocator isn"t appropriate for large blocks. */


    

p

=

(

PyIntObject *

)

PyMem_MALLOC

(

sizeof

(

PyIntBlock

));


 


    

// 建立失敗(內存耗光了)


    

if

(

p

==

NULL

)


        

return

(

PyIntObject *

)

PyErr_NoMemory

();


 


    

// block_list指向新的PyIntBlock節點


    

((

PyIntBlock *

)

p

)

->

next

=

block_list

;


    

block_list

=

(

PyIntBlock *

)

p

;


 


    

/* Link the int objects together, from rear to front, then return


       the address of the last int object in the block. */


 


    

// p=block裡面 PyIntObjects數組頭地址, q是尾地址


    

p

= &

((

PyIntBlock *

)

p

)

->

objects

[

0

];


    

q

=

p

+

N_INTOBJECTS

;


 


    

// 從尾部開始向首部移動, 利用對象里的ob_type指針(相當於使用這個欄位, ob_type不作為原來的用途), 建立起一個單向鏈表


    

// 這個單向鏈表的頭部是數組的最後一個


    

while

(

--

q

>

p

)


        

Py_TYPE

(

q

)

=

(

struct

_typeobject *

)(

q

-

1

);


    

Py_TYPE

(

q

)

=

NULL

;

// 單向鏈表最後一個元素的next指向null


 


    

// 返回單向鏈表的頭地址!!!


    

return

p

+

N_INTOBJECTS

-

1

;


 


}




新建第一個時, 只有一個





從裡面拿整數時, 取free_list指向的節點, 然後free_list指向鏈表下一個節點




當一個block用完了之後, 即free_list=NULL, 此時要新建另一個PyIntBlock




新建第二個





通用整數對象池3 – 刪除一個整數時




定義





#define PyInt_CheckExact(op) ((op)->ob_type == &PyInt_Type)


 


static

void


int_dealloc

(

PyIntObject *

v

)


{


    

// 是整數類型, 將對象放入free_list單向鏈表頭


    

if

(

PyInt_CheckExact

(

v

))

{


        

Py_TYPE

(

v

)

=

(

struct

_typeobject *

)

free_list

;


        

free_list

=

v

;


    

}


    

else


        

Py_TYPE

(

v

)

->

tp_free

((

PyObject *

)

v

);

//不是整數類型, 對應類型析構


}




可以看到, 回收的時候, 把空間給放回到free_list了, 後面接著用




block_list維護著所有PyIntBlock列表, 查看源碼注釋可以看到





PyIntBlocks are never returned

to

the


system before shutdown

(

PyInt_Fini

).




即, PyIntBlock申請的所有內存, 在Python結束之前, 都不會被釋放





所以

,

使用

range

(

100000

),

運行後

,

雖然程序結束了

,

但是整數佔用空間還在

.


 


建議對大範圍的序列生成使用

xrange


 


python3

.

x

不用擔心這個問題




本系列




  • 《Python 源碼閱讀:類型》



  • 《Python 源碼閱讀:對象》



  • 《Python 源碼閱讀:String》




看完本文有收穫?請轉

發分享給更多人


關注「P

ython開發者」,提升Python技能


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發者 的精彩文章:

Python 中的作用域規則和閉包簡析
Python 源碼閱讀: String
Python 中的屬性訪問與描述符
Python 源碼閱讀:對象
構建多層感知器神經網路對數字圖片進行文本識別

TAG:Python開發者 |