Python引用计数的简单总结

最近在用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引用计数的简单总结)