問題緣起於python-cn郵件清單的一個問題:http://groups.google.it/group/python-cn/browse_thread/thread/758891b4342eb2d9/92c12bf6acd667ac
有趣的是,為什麼在Python2.4中sys.getrefcount(11111111)的結果是2,到了Python2.5中卻搖身一變,變成了 3?更令人驚奇的是,如果你在Python2.5的IDLE中運行sys.getrefcount(111111),你會驚奇地發現,好了,現在的結果又 變成2了。我們知道sys.getrefcount輸出的是一個對象的引用計數,為什麼相同的代碼,相同的對象,在不同的運行環境中的引用計數卻不同了? 原因是,我們有一個致命的疏漏。
在考察sys.getrefcount時,我們只看到了它的運行結果,但是實際上在啟動並執行背後還有一個更加重要的幕後推手——編譯。為了確認編譯是否對對象的引用計數產生影響,我們來考察幾個例子:
1、互動式環境下:
Python 2.5 (r25:51908, May 27 2007, 09:33:26) [MSC v.1310 32 bit (Inte
32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getrefcount(11111111)
3
2、檔案方式執行 :
[ref.py]
import sys
print sys.getrefcount(11111111)
結果如下:
F:/compile/Python-2.5/PCbuild>python ref.py
3
3、檔案方式執行(避開編譯影響)
[demo.py]
import ref
結果如下:
F:/compile/Python-2.5/PCbuild>python demo.py
2
由此可見,實際上多餘的引用是由Python的編譯過程貢獻的,在1、2兩種執行方式下,Python都會在開始時啟用一個編譯的動作;而在執行方式3下,import機制會導致產生一個ref.pyc檔案,所以不會在每次執行時都會啟用編譯動作。
但是,但是,我們看到在Python2.5的互動式環境中和IDLE環境中,都需要進行編譯,而輸出的結果卻不同,這又是什麼原因呢?嗯,這就需要從Python的源碼中找原因了。我們先來看看sys.getrefcount到底輸出了個什麼玩意兒:
[sysmodule.c]static PyObject *
sys_getrefcount(PyObject *self, PyObject *arg)
...{
if(arg != NULL && PyInt_Check(arg)) ...{
if(PyInt_AsLong(arg) == 11111111) ...{
printf("in sys_getrefcount ");
}
}
return PyInt_FromSsize_t(arg->ob_refcnt);
}
原來輸出的東西就是PyObject中的ob_refcnt值,為了真正搞清楚引用計數的變化情況,我們就來修改Python原始碼,對每一次引用計數的變化進行監控,這需要修改到Python中用於改變引用計數的兩個宏:
[object.h]#define Py_INCREF(op) (
robertincref((PyObject*)(op)) ,
(op)->ob_refcnt++)
#define Py_DECREF(op)
if (robertdecref((PyObject*)(op)) ,
--(op)->ob_refcnt != 0)
_Py_CHECK_REFCNT(op)
else
_Py_Dealloc((PyObject *)(op))
[object.c]void robertincref(PyObject* obj) ...{
if(PyInt_Check(obj) && (PyInt_AsLong(obj) == 11111111)) ...{
long refcnt = obj->ob_refcnt;
printf("increase ref count from %d to %d ", refcnt, refcnt+1);
}
}
void robertdecref(PyObject* obj) ...{
if(PyInt_Check(obj) && (PyInt_AsLong(obj) == 11111111)) ...{
long refcnt = obj->ob_refcnt;
printf("decrease ref count from %d to %d ", refcnt, refcnt-1);
}
}
同時我們還在pyc檔案的讀入點r_object中,整數對象的建立點PyInt_FromLong中,引用計數獲得點sys_getrefcount中添加對11111111的監控代碼,最後的輸出結果如下:
執行方式2(Python2.5互動式環境):
create 11111111 in PyInt_FromLong!
increase ref count from 1 to 2 //監視Py_INCREF的結果
decrease ref count from 2 to 1 //監視Py_INCREF的結果
increase ref count from 1 to 2
increase ref count from 2 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
decrease ref count from 3 to 2
LOAD_CONST for 11111111
increase ref count from 2 to 3
in sys_getrefcount //這一行顯示Python虛擬機器目前在sys_getrefcount函數中
decrease ref count from 3 to 2
3 //這個是輸出的結果
decrease ref count from 2 to 1
decrease ref count from 1 to 0
執行方式3(載入pyc檔案):
read 11111111 in r_object
create 11111111 in PyInt_FromLong!
LOAD_CONST for 11111111
increase ref count from 1 to 2
in sys_getrefcount
decrease ref count from 2 to 1
2
decrease ref count from 1 to 0
其中的LOAD_CONST是Python中的一個位元組碼,是sys.getrefcount編譯後的結果,我們在位元組碼指令的實現代碼中也添加了監控代碼。
執行方式2的編譯過程會頻繁地對整數對象進行引用計數的調整;而執行方式3的動作序列則很清晰:
1、Python虛擬機器通過r_object函數從pyc檔案中讀入整數11111111,這會啟用PyInt_FromLong,這裡建立整數對象,並將ob_refcnt設定為1
2、ref.py中的print sys.getrefcount (11111111)最終編譯得到的位元組碼指令中有LOAD_CONST,這條位元組碼指令會通過Py_INCREF增加整數對象的引用計數,這時為2
在執行方式2代表的互動式環境中,編譯過程對對象的引用計數產生了巨大的影響,而在IDLE中,這種影響更為頻繁,我們看一下IDLE方式執行時的輸出結果:
create 11111111 in PyInt_FromLong!
increase ref count from 1 to 2
decrease ref count from 2 to 1
increase ref count from 1 to 2
increase ref count from 2 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
decrease ref count from 3 to 2
decrease ref count from 2 to 1
create 11111111 in PyInt_FromLong!
increase ref count from 1 to 2
decrease ref count from 2 to 1
increase ref count from 1 to 2
increase ref count from 2 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
decrease ref count from 3 to 2
decrease ref count from 2 to 1
create 11111111 in PyInt_FromLong!
increase ref count from 1 to 2
decrease ref count from 2 to 1
increase ref count from 1 to 2
increase ref count from 2 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
increase ref count from 3 to 4
decrease ref count from 4 to 3
decrease ref count from 3 to 2
decrease ref count from 2 to 1
decrease ref count from 1 to 0
decrease ref count from 1 to 0
read 11111111 in r_object
create 11111111 in PyInt_FromLong!
decrease ref count from 1 to 0
LOAD_CONST for 11111111
increase ref count from 1 to 2
in sys_getrefcount
decrease ref count from 2 to 1