buk*_*zor 5 python refcounting python-internals
我不明白以下行为.
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())
Run Code Online (Sandbox Code Playgroud)
输出是:
$ python demo.py
3
3
4
4
Run Code Online (Sandbox Code Playgroud)
这与“快速局部变量”有关,它存储为一对匹配的元组,用于快速整数索引(一个用于名称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;
}
Run Code Online (Sandbox Code Playgroud)
反过来,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;
}
Run Code Online (Sandbox Code Playgroud)
这是为框架的局部变量分配普通旧字典并将任何“快速”变量填充到其中的位。由于新的字典被附加到框架结构上(如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);
}
Run Code Online (Sandbox Code Playgroud)
ani*_*haw -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())
Run Code Online (Sandbox Code Playgroud)
这是代码的输出
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>}]
Run Code Online (Sandbox Code Playgroud)
似乎即使在垃圾收集之后它也会缓存 dict 值以供将来调用 locals() 。