我不明白以下行为。
locals()
如何产生新的引用?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
答案 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()。