了解 python 中的引用计数

Understanding reference count in python

我正在尝试了解 python 中引用计数的工作原理。我创建了一个变量 x 并为其赋值 10。所以基本上 x 指向存储 class 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 是 table 常量的索引,常量存储在 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

而不是 LOAD_CONST 只是 LOAD_NAME a 碰巧指向的任何东西。对象 10 本身在 code 对象的任何地方都没有被引用。

更新: 第二个引用的来源相当模糊,但它来自使用 Arena structure for efficient memory management of AST nodes and the like. The arena also maintains a list (as in an actual Python list) of Python objects parsed in the AST, in the case of numbers that happens here: https://github.com/python/cpython/blob/fee96422e6f0056561cf74fef2012cc066c9db86/Python/ast.c#L2144 的 AST 解析器(其中 PyArena_AddPyObject 添加所述列表的对象)。 IIUC 这个列表的存在只是为了确保从 AST 解析的文字在某处至少有一个引用。

在用于编译和 运行ning 交互语句的实际 C 代码中,arena 没有被释放 until after the compiled statement has been executed,此时第二个额外的引用消失了。