美文网首页
Python引用计数的简单总结

Python引用计数的简单总结

作者: whosemario | 来源:发表于2017-02-23 13:43 被阅读0次

    最近在用Python原生的API写一些逻辑,被维护PyObject引用计数搞得很是头疼,这里做些简单的总结,说明在什么时候一个PyObject会增加引用计数。

    Python有几个存储操作:

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

    其实比较常见的也就是前两个,解析STORE_FAST说明一下Python是怎样操作引用计数的。

    g_a = 1
    def func(b):
        global g_a
        g_a = 1
        b = 2
        c = 3
    

    上面func对应的字节码如下:

    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()
    

    可以看出来SETLOCAL只做减引用,不做加引用,我们可以认为stack_pointer里面的Object已经加过引用计数了,因此很容易去想到看一下LOAD_FAST的实现:

    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]出会进行加引用,再PUSH到stack_pointer中。

    好的,基本的逻辑也说搞明白了,总结如下:

    任何的Object在PUSH到stack前,会有个地方进行加引用(这个地方可以是call_function内部,也可能就是字节码的内部实现,例如LOAD_CONST、LOAD_FAST等), POP处理的Object是不需要再加引用的!

    那么什么时候对POP处理的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]处解引用了,因为SetItem的过程中会增加引用,因此v变量已经是个临时变量了,需要将v再减一次引用。

    相关文章

      网友评论

          本文标题:Python引用计数的简单总结

          本文链接:https://www.haomeiwen.com/subject/ijcbwttx.html