最近在用Python原生的API寫(xiě)一些邏輯,被維護(hù)PyObject引用計(jì)數(shù)搞得很是頭疼,這里做些簡(jiǎn)單的總結(jié),說(shuō)明在什么時(shí)候一個(gè)PyObject會(huì)增加引用計(jì)數(shù)。
Python有幾個(gè)存儲(chǔ)操作:
- STORE_FAST
- STORE_GLOBAL
- STORE_NAME
- STORE_MAP
- ...
其實(shí)比較常見(jiàn)的也就是前兩個(gè),解析STORE_FAST說(shuō)明一下Python是怎樣操作引用計(jì)數(shù)的。
g_a = 1
def func(b):
global g_a
g_a = 1
b = 2
c = 3
上面func對(duì)應(yīng)的字節(jié)碼如下:
3 0 LOAD_CONST 1 (1)
3 STORE_GLOBAL 0 (g_a)
4 6 LOAD_CONST 2 (2)
9 STORE_FAST 0 (b)
5 12 LOAD_CONST 3 (3)
15 STORE_FAST 1 (c)
18 LOAD_CONST 0 (None)
21 RETURN_VALUE
看一下STORE_FAST源碼:
TARGET(STORE_FAST)
{
v = POP();
SETLOCAL(oparg, v);
FAST_DISPATCH();
}
#define SETLOCAL(i, value) do { PyObject *tmp = GETLOCAL(i); \
GETLOCAL(i) = value; \
Py_XDECREF(tmp); } while (0)
#define GETLOCAL(i) (fastlocals[i])
#define BASIC_POP() (*--stack_pointer)
#define POP() BASIC_POP()
可以看出來(lái)SETLOCAL只做減引用,不做加引用,我們可以認(rèn)為stack_pointer里面的Object已經(jīng)加過(guò)引用計(jì)數(shù)了,因此很容易去想到看一下LOAD_FAST的實(shí)現(xiàn):
TARGET(LOAD_FAST)
{
x = GETLOCAL(oparg);
if (x != NULL) {
Py_INCREF(x); [1]
PUSH(x);
FAST_DISPATCH();
}
format_exc_check_arg(PyExc_UnboundLocalError,
UNBOUNDLOCAL_ERROR_MSG,
PyTuple_GetItem(co->co_varnames, oparg));
break;
}
在[1]出會(huì)進(jìn)行加引用,再PUSH到stack_pointer中。
好的,基本的邏輯也說(shuō)搞明白了,總結(jié)如下:
任何的Object在PUSH到stack前,會(huì)有個(gè)地方進(jìn)行加引用(這個(gè)地方可以是call_function內(nèi)部,也可能就是字節(jié)碼的內(nèi)部實(shí)現(xiàn),例如LOAD_CONST、LOAD_FAST等), POP處理的Object是不需要再加引用的!
那么什么時(shí)候?qū)OP處理的Object解引用呢?這種情況就比較多了,比如如下代碼:
TARGET(STORE_GLOBAL)
{
w = GETITEM(names, oparg);
v = POP();
err = PyDict_SetItem(f->f_globals, w, v);
Py_DECREF(v); [1]
if (err == 0) DISPATCH();
break;
}
我們看到[1]處解引用了,因?yàn)镾etItem的過(guò)程中會(huì)增加引用,因此v變量已經(jīng)是個(gè)臨時(shí)變量了,需要將v再減一次引用。