6

我不明白以下行为。

  • 如何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
4

3 回答 3

5

这与 'fast locals' 有关,它们存储为匹配的元组对,用于快速整数索引(一个用于 names f->f_code->co_varnames,一个用于 values f->f_localsplus)。当locals()被调用时,fast-locals 被转换成一个标准的 dict 并附加到框架结构上。cpython 代码的相关位如下。

这是locals(). 它只不过是 call 而已PyEval_GetLocals

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

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

反过来PyEval_GetLocals,只做 call 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;
}

这是为框架的局部变量分配一个普通的旧字典并将任何“快速”变量填充到其中的位。由于新 dict 被附加到框架结构(as 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);
}
于 2014-03-08T05:21:58.743 回答
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() 不收集本地字典,您可以通过打印 id 来查看它,它是两次返回的同一个对象,它以某种方式为此帧进行了记忆,因此没有被收集。

于 2014-03-08T00:30:11.177 回答
-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()。

于 2014-03-08T00:16:29.160 回答