了解python中的引用计数



我正在努力了解引用计数在python中是如何工作的。我创建了一个变量x,并给它赋值10。所以基本上x指向存储int(10(类对象的内存位置。现在,当我试图得到x和10的引用计数时,我得到了两个不同的引用计数。如果x指向存储10的同一存储位置,那么为什么它们具有不同的参考计数?

>>> import sys
>>> sys.getrefcount(10)
12
>>> a = 10
>>> sys.getrefcount(10)
13
>>> sys.getrefcount(a)
11

当您直接调用sys.getrefcount(10)时,调用本身会增加引用计数。呼叫站点上有一个10的参考,至少还有一个,原因我记不清了。

更详细的答案是:当你在交互式提示中运行一条语句时,该语句会被编译成字节码,然后由解释器执行。字节码存储在code对象中,您可以通过使用compile()内置的自己编译一条语句来检查该对象

>>> a = 10
>>> c = compile('sys.getrefcount(10)', '<stdin>', 'single')
>>> c
<code object <module> at 0x7f4def343270, file "<stdin>", line 1>

我们可以使用dis模块来检查编译后的字节码:

>>> dis.dis(c)
1           0 LOAD_NAME                0 (sys)
2 LOAD_ATTR                1 (getrefcount)
4 LOAD_CONST               0 (10)
6 CALL_FUNCTION            1
8 PRINT_EXPR
10 LOAD_CONST               1 (None)
12 RETURN_VALUE

您可以看到CALL_FUNCTION之前的字节码是LOAD_CONST 10。但是它怎么知道10是要加载的常数呢?实际的字节码指令是LOAD_CONST(0),其中0是存储在code对象中的常数表的索引

>>> c.co_consts
(10, None)

因此,10的一个新引用(暂时(就住在这里。

而如果我们这样做:

>>> c2 = compile('sys.getrefcount(a)', '<stdin>', 'single')
>>> dis.dis(c2)
1           0 LOAD_NAME                0 (sys)
2 LOAD_ATTR                1 (getrefcount)
4 LOAD_NAME                2 (a)
6 CALL_FUNCTION            1
8 PRINT_EXPR
10 LOAD_CONST               0 (None)
12 RETURN_VALUE

a指向的对象中只有LOAD_NAME,而不是LOAD_CONST。对象10本身在code对象中的任何位置都没有被引用。

更新:第二个引用的来源很模糊,但它来自AST解析器,该解析器使用Arena结构对AST节点等进行有效的内存管理。arena还维护了在AST中解析的Python对象的列表(如在实际的Pythonlist中(,在这里发生数字的情况下:https://github.com/python/cpython/blob/fee96422e6f0056561cf74fef2012cc066c9db86/Python/ast.c#L2144(其中PyArena_AddPyObject将对象添加到所述列表(。IIUC这个列表的存在只是为了确保从AST解析的文字在某个地方至少有一个引用。

在用于编译和运行交互式语句的实际C代码中,直到编译后的语句执行完毕,第二个额外的引用才被释放。

最新更新