为啥调用 locals() 会添加引用?
Posted
技术标签:
【中文标题】为啥调用 locals() 会添加引用?【英文标题】:why does a call to locals() add a reference?为什么调用 locals() 会添加引用? 【发布时间】:2014-03-08 00:00:16 【问题描述】:我不明白以下行为。
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
【问题讨论】:
我的猜测是函数调用的局部变量dict是在第一次需要时创建的,然后被缓存。不过,我不太了解 Python 的内部结构,不知道 Python 是否真的这样做了。 @user2357112:你的猜测是正确的,在我的回答中得到了证明。 【参考方案1】:这与“快速本地”有关,它们存储为匹配的元组对,用于快速整数索引(一个用于名称f->f_code->co_varnames
,一个用于值f->f_localsplus
)。当locals()
被调用时,fast-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;
这是为框架的局部变量分配一个普通的旧字典并将任何“快速”变量填充到其中的位。由于新 dict 被附加到框架结构上(如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);
【讨论】:
不错!你在哪里找到这些功能的?我很难弄清楚大多数内置函数是在哪里实现的。 @user2357112 我的经理是 cpython 大师,把它们粘贴给我 =X 就是说,bltinmodule.c
实现了 __builtins__
和 frameobject.c
实现了 python 的框架对象。【参考方案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 来查看它,它是两次返回的同一个对象,它以某种方式为这一帧记忆,因此没有被收集。
【讨论】:
【参考方案3】:这是因为 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()。
【讨论】:
OP 知道locals
这样做。问题是 dict 及其引用会消失,因为没有存储对 dict 的显式引用。
另外,locals() 并没有说它创建一个字典。由于本地命名空间只是从名称到值的映射,我希望 dict 已经存在。编辑 locals() 的返回值的事实也符合这种直觉。
Ned Batcheler,coverage.py 的维护者,wrote about locals "locals() 函数比乍一看更复杂。返回值是一个字典,它是本地符号表的副本。这就是为什么更改 dict 可能实际上不会更改局部变量的原因。”
@bukzor:你所期望的和 CPython 实际做的是两件不同的事情;-p。局部变量不存储在 CPython 中的 dict 中的事实是,我想我引用了 Tim Peters,这是其中最重要的优化之一。以上是关于为啥调用 locals() 会添加引用?的主要内容,如果未能解决你的问题,请参考以下文章
为啥当我在 cfoutput 中调用函数时,ColdFusion 会添加空格?