Python引用計(jì)數(shù)的簡(jiǎn)單總結(jié)

最近在用Python原生的API寫(xiě)一些邏輯,被維護(hù)PyObject引用計(jì)數(shù)搞得很是頭疼,這里做些簡(jiǎn)單的總結(jié),說(shuō)明在什么時(shí)候一個(gè)PyObject會(huì)增加引用計(jì)數(shù)。

Python有幾個(gè)存儲(chǔ)操作:

  1. STORE_FAST
  2. STORE_GLOBAL
  3. STORE_NAME
  4. STORE_MAP
  5. ...

其實(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再減一次引用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容