Python 的內建對象存放在源代碼的Objects
目錄下。
intobject.c
用于整數對象
在 Python 中,整數分為小整數對象
和大整數對象
小整數對象
由于數值較小的整數對象在內存中會很頻繁地使用,如果每次都向內存申請空間、請求釋放,會嚴重影響 Python 的性能。好在 整數對象 屬于不可變對象,可以被共享而不會被修改導致問題,所以為 小整數對象 劃定一個范圍,即小整數對象池,在Python運行時初始化并創建范圍內的所有整數,這個范圍內的 整數對象是被共享的,即一次創建,多次共享引用。
那么這個范圍是多少呢?從源文件中可以看到,而且,用戶可以自行調整,只是每次都要在源文件中修改,而后進行編譯、安裝。
小整數池的范圍:
#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];
從源代碼可以看出
define NSMALLPOSINTS 257
,范圍的右邊界
define NSMALLNEGINTS 5
,范圍的左邊界
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive)
,[-5, 257)
大整數對象
但是,整數對象很多,不一定都是小整數對象,又不能將所有整數對象都放入內存。于是,Python 提供了一個可擴展的內存空間,稱為通用整數對象池
,誰需要用就給誰用,這樣免去了申請空間,又能提高一些效率。
這個空間是一個PyIntBlock結構,是用一個單向列表連接一串內存(block),這個列表由block_list
維護,而每個 block 維護一個 整數對象數組(Objects),用于存放被緩存的整數對象。block_list
的內容是最新創建的 block。
小整數對象池 也在block_list
上。
Python 使用一個單向鏈表管理全部 block 的 objects 中的所有空閑內存,由free_list
指出下一個可用的空閑內存。如果當前沒有空閑內存,free_list
為NULL
,會創建新的內存。
當整數對象的引用計數變為0,會銷毀對象,但并不會釋放空閑出來的內存,即將內存交還系統,而是重新加入free_list
。
hack
使用Xcode
修改打印整數對象的方法
原始文件
/* ARGSUSED */
static int
int_print(PyIntObject *v, FILE *fp, int flags)
/* 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;
}
修改后,可以打印部分小整數地址池中,整數對象的引用次數、所在內存地址、下一個可用的空閑內存地址
/* ARGSUSED */
static int values[10];
static int refcounts[10];
static int
int_print(PyIntObject *v, FILE *fp, int flags)
/* flags -- not used but required by interface */
{
PyIntObject* intObjectPtr;
PyIntBlock *p = block_list;
PyIntBlock *last = NULL;
int count = 0;
int i;
while(p != NULL)
{
++count;
last = p;
p = p->next;
}
intObjectPtr = last->objects;
intObjectPtr += N_INTOBJECTS - 1;
printf(" address @%p\n", v);
for(i = 0; i < 10; ++i, -- intObjectPtr)
{
values[i] = intObjectPtr -> ob_ival;
refcounts[i] = intObjectPtr -> ob_refcnt;
}
printf(" value : ");
for(i = 0; i < 8; ++i)
{
printf("%d\t", values[i]);
}
printf("\n");
printf(" refcnt : ");
for(i = 0; i < 8; ++i)
{
printf("%d\t", refcounts[i]);
}
printf("\n");
printf(" block_list count : %d\n", count);
printf(" free_list : %p\n", free_list);
return 0;
/* long int_val = v->ob_ival;
Py_BEGIN_ALLOW_THREADS
fprintf(fp, "%ld", int_val);
Py_END_ALLOW_THREADS
return 0;*/
}
保存并編譯、安裝,運行修改后的 Python
Python 2.6.9 (unknown, Nov 1 2015, 20:22:05)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.1.76)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> i = -9999
>>> i
address @0x7fe412f16470 # -9999 所在的內存地址
value : -5 -4 -3 -2 -1 0 1 2 # 能夠顯示的 -5 ~ 2
refcnt : 1 1 1 1 35 105 64 41 # 引用計數器,可以看到,小整數已經被Python自身使用了多次
block_list count : 8 # block 數量
free_list : 0x7fe412f16488 # 下一個可用的空閑內存地址
>>>
>>> a = -258
>>> a
address @0x7fe412f16488 # -258 的內存地址,是上面`free_list`指出的空閑內存
value : -5 -4 -3 -2 -1 0 1 2
refcnt : 1 1 1 1 35 105 64 41
block_list count : 8
free_list : 0x7fe412f164a0 # 新的空閑內存地址
>>> b = -258
>>> b
address @0x7fe412f164a0 # 上一個的空閑內存地址,可以看出,對于多次創建的大整數對象,即使值一樣,也是不同的內存地址
value : -5 -4 -3 -2 -1 0 1 2
refcnt : 1 1 1 1 35 105 64 41
block_list count : 8
free_list : 0x7fe412f164b8
>>> del b # 釋放 b 的內存空間
>>> a
address @0x7fe412f16488
value : -5 -4 -3 -2 -1 0 1 2
refcnt : 1 1 1 1 35 105 64 41
block_list count : 8
free_list : 0x7fe412f164a0 # 刪除 b 后,新的空閑內存重新加入`free_list`,沒有歸還給系統
>>> c1 = -5 # 屬于小整數對象池
>>> c1
address @0x7fe412f033d8
value : -5 -4 -3 -2 -1 0 1 2
refcnt : 5 1 1 1 35 105 64 41 # -5 引用此時為 5
block_list count : 8
free_list : 0x7fe412f164b8
>>> c2 = -5 # 同上
>>> c2
address @0x7fe412f033d8 # 兩次創建的相同小整數對象,指向了相同的內存地址
value : -5 -4 -3 -2 -1 0 1 2
refcnt : 6 1 1 1 35 105 64 41 # -5 的引用次數加一,變為 6
block_list count : 8
free_list : 0x7fe412f164b8
>>>
整數對象的說明文件內置在源代碼中:
PyDoc_STRVAR(int_doc,
"int(x[, base]) -> integer\n\
\n\
Convert a string or number to an integer, if possible. A floating point\n\
argument will be truncated towards zero (this does not include a string\n\
representation of a floating point number!) When converting a string, use\n\
the optional base. It is an error to supply a base when converting a\n\
non-string. If base is zero, the proper base is guessed based on the\n\
string content. If the argument is outside the integer range a\n\
long object will be returned instead.");
int_doc
就是整數對象的__doc__
屬性
參考資料
《Python 源碼剖析》第二章:整數對象