为什么调用locals()会添加引用?

时间:2014-03-08 00:00:16

标签: python python-internals refcounting

我不明白以下行为。

  • locals()如何产生新的引用?
  • 为什么gc.collect没有删除它?我没有将locals()的结果分配到任何地方。

X

import gc

from sys import getrefcount

def trivial(x): return x

def demo(x):
    print getrefcount(x)
    x = trivial(x)
    print getrefcount(x)
    locals()
    print getrefcount(x)
    gc.collect()
    print getrefcount(x)


demo(object())

输出结果为:

$ python demo.py 
3
3
4
4

3 个答案:

答案 0 :(得分:4)

这与快速的本地人有关'它们存储为匹配的元组对,用于快速整数索引(一个用于名称f->f_code->co_varnames,一个用于值f->f_localsplus)。当调用locals()时,快速局部变换为标准字典并固定到框架结构上。 cpython代码的相关位在下面。

这是locals()的实施功能。它只是调用PyEval_GetLocals

static PyObject *
builtin_locals(PyObject *self)
{
    PyObject *d;

    d = PyEval_GetLocals();
    Py_XINCREF(d);
    return d;
}   

反过来,PyEval_GetLocals只会调用PyFrame_FastToLocals

PyObject *
PyEval_GetLocals(void)
{   
    PyFrameObject *current_frame = PyEval_GetFrame();
    if (current_frame == NULL)
        return NULL;
    PyFrame_FastToLocals(current_frame);
    return current_frame->f_locals;
}

这是为框架的局部变量分配一个普通的字典并填充任何" fast"变量进入它。由于新的字典被添加到框架结构上(如f->f_locals),任何"快速"变量在调用locals()时获得额外的引用。

void
PyFrame_FastToLocals(PyFrameObject *f)
{
    /* Merge fast locals into f->f_locals */
    PyObject *locals, *map;
    PyObject **fast;
    PyObject *error_type, *error_value, *error_traceback;
    PyCodeObject *co;
    Py_ssize_t j;
    int ncells, nfreevars;
    if (f == NULL)
        return;
    locals = f->f_locals;
    if (locals == NULL) {
        /* This is the dict that holds the new, additional reference! */
        locals = f->f_locals = PyDict_New();  
        if (locals == NULL) {
            PyErr_Clear(); /* Can't report it :-( */
            return;
        }
    }
    co = f->f_code;
    map = co->co_varnames;
    if (!PyTuple_Check(map))
        return;
    PyErr_Fetch(&error_type, &error_value, &error_traceback);
    fast = f->f_localsplus;
    j = PyTuple_GET_SIZE(map);
    if (j > co->co_nlocals)
        j = co->co_nlocals;
    if (co->co_nlocals)
        map_to_dict(map, j, locals, fast, 0);
    ncells = PyTuple_GET_SIZE(co->co_cellvars);
    nfreevars = PyTuple_GET_SIZE(co->co_freevars);
    if (ncells || nfreevars) {
        map_to_dict(co->co_cellvars, ncells,
                    locals, fast + co->co_nlocals, 1);
        /* If the namespace is unoptimized, then one of the
           following cases applies:
           1. It does not contain free variables, because it
              uses import * or is a top-level namespace.
           2. It is a class namespace.
           We don't want to accidentally copy free variables
           into the locals dict used by the class.
        */
        if (co->co_flags & CO_OPTIMIZED) {
            map_to_dict(co->co_freevars, nfreevars,
                        locals, fast + co->co_nlocals + ncells, 1);
        }
    }
    PyErr_Restore(error_type, error_value, error_traceback);
}

答案 1 :(得分:2)

我在您的演示代码中添加了一些打印件:

#! /usr/bin/python

import gc

from sys import getrefcount

def trivial(x): return x

def demo(x):
    print getrefcount(x)
    x = trivial(x)
    print getrefcount(x)
    print id(locals())
    print getrefcount(x)
    print gc.collect(), "collected"
    print id(locals())
    print getrefcount(x)


demo(object())

然后输出(在我的机器上):

3
3
12168320
4
0 collected
12168320
4

locals()实际上创建了一个包含x的ref的dict,因此是ref inc。 gc.collect()不收集本地dict,你可以通过打印id看到它,它是同一个返回两次的对象,它以某种方式被记忆为这个帧,因此没有被收集。

答案 2 :(得分:-1)

这是因为locals()创建了一个实际的字典并将x放入其中,因此增加了x的引用计数,这个字典可能被缓存了。

所以我通过添加两行来改变代码

import gc

from sys import getrefcount

def trivial(x): return x

def demo(x):
   print getrefcount(x)
   x = trivial(x)
   print getrefcount(x)
   print "Before Locals ",  gc.get_referrers(x)
   locals()
   print "After Locals ",  gc.get_referrers(x)
   print getrefcount(x)
   gc.collect()
   print getrefcount(x)
   print "After garbage collect", gc.get_referrers(x)

demo(object())   

这是代码的输出

3
3
Before Locals  [<frame object at 0x1f1ee30>]
After Locals  [<frame object at 0x1f1ee30>, {'x': <object object at 0x7f323f56a0c0>}]
4
4
After garbage collect [<frame object at 0x1f1ee30>, {'x': <object object at 0x7f323f56a0c0>}]

似乎它正在缓存dict值,即使在垃圾收集之后,以便将来调用locals()。