【问题标题】:Why are Python Ref Counts to small integers surprisingly high?为什么 Python Ref Counts to small integers 高得惊人?
【发布时间】:2016-07-07 19:26:23
【问题描述】:

this 的回答中,我找到了一种方法来获取 Python 中对象的引用计数。

他们提到使用sys.getrefcount()。我试过了,但我得到了意想不到的结果。当有 1 个引用时,似乎计数为 20。这是为什么呢?

我查看了documentation,但似乎无法解释原因。

【问题讨论】:

  • 试试sys.getrefcount(257),它可能会明显下降。
  • 真奇怪,我从来没有用 257 创建过 var。为什么它返回 3?
  • 也试试sys.getrefcount("Valerie")。 (与此处的其他评论者保持同步)
  • 再次说明实现细节,但从 -5 到 257 的小整数被保留,因此该范围的引用计数会高得多
  • 谢谢大家,刚刚学到了很多关于python的知识。我想这些是他们在 Hello World 教程中跳过的内容:)

标签: python python-3.x reference-counting


【解决方案1】:

对一个对象有很多引用有很多原因。追查哪一个可能是困难的,并决定它是否值得,可能会绕过你的兴趣水平。引用计数是调试应用程序和 Python 变体的开发人员的主要兴趣所在。

  • Python 试图为每个引用只保留一个实际值。因此,您在示例中拥有的 100 将与递归调用的某些内部限制相同的 100 或与当前循环索引相同的 100 相同。
  • Python 保留对一些常见对象的额外引用,包括低整数。对 1,234,567 的引用计数与对 20 的计数不同。
  • 许多函数会记住并保留对最近参数的引用。
  • 一些解释器保留对最近值和最近行中引用的值的引用。例如,之前的返回值存储在“_”中。这意味着在解释器中运行和从命令行运行会给出不同的答案。
  • 与所有引用计数方案一样,也存在错误。例如,PyTuple_GetItem() 有一些值得怀疑的选择。

在 PyPi、C-Python 和 IPython 中,确切的引用计数和这些计数的含义会有所不同。引用计数很少是在 Python 中发现奇怪行为的好工具。

【讨论】:

    【解决方案2】:

    在第一次调用sys.getrefcount 时,该对象恰好有20 次引用。这不仅仅是您创建的引用;在其他模块和 Python 内部,还有各种其他对它的引用,因为(这是一个实现细节)Python 的标准实现只创建一个 100 对象并将其用于所有出现的 100 Python 程序。

    【讨论】:

    • 更相关的是,如果您创建自己的对象(列表、元组、类实例),您将获得预期的行为。
    【解决方案3】:

    阅读Python2.7的源代码很有趣,它写得很好,读起来很清楚。 (如果你想在家玩,我指的是 2.7.12 版本。)理解代码的一个好地方是优秀的系列讲座:C Python Internals,它从初学者的角度开始。

    与我们相关的关键代码(用 C 编写)出现在文件“Objects/intobject.c”中(为了清楚起见,我删除了一些#ifdef 代码并稍微修改了新 Integer 对象的创建):

        #define NSMALLPOSINTS           257
        #define NSMALLNEGINTS           5
        static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
    
        PyObject *
        PyInt_FromLong(long ival)
        {
            register PyIntObject *v;
            if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
                v = small_ints[ival + NSMALLNEGINTS];
                Py_INCREF(v);
                return (PyObject *) v;
            }
            /* Inline PyObject_New */
            v = (PyIntObject *)Py_TYPE(v);
            PyObject_INIT(v, &PyInt_Type);
            v->ob_ival = ival;
            return (PyObject *) v;
        }
    

    所以本质上它会创建一个包含从 -5 到 256 的所有数字的预设数组,并尽可能使用这些对象(使用 Py_INCREF 宏增加它们的引用计数)。如果没有,它将创建一个全新的 PyInt_Type 对象,该对象的引用计数为 1。

    为什么每个数字的引用计数似乎都是 3(实际上几乎是任何新对象)的奥秘只有在您查看 Python 生成的字节码时才会揭开。虚拟机使用值栈(有点像 Forth 中的)运行,每次将对象放入值栈时,它都会增加引用计数。

    所以我怀疑正在发生的事情是您的代码本身提供了您看到的所有 3 个引用,因为对于不在小值列表中的数字,您应该得到一个唯一的对象。第一个引用大概在 getrefcount 的调用者调用时的值堆栈中;第二个在 getrefcount 帧的局部变量列表中;第三个可能在 getrefcount 帧中的值堆栈上,同时它会查找其引用计数。

    如果您想进一步研究问题,一个有用的工具是“编译”命令和“dis”模块中的“dis”命令(反汇编),它们一起允许您读取实际字节由任何一段 Python 代码生成的代码,并且应该可以帮助您准确了解创建第三个引用的时间和地点。

    对于小值的较高引用计数,当您启动 Python 时,它会自动加载整个标准库并在您开始解释自己的代码之前运行大量 Python 模块初始化代码。这些模块拥有自己的副本,其中包含许多小整数(以及也是唯一的 None 对象)。

    【讨论】:

    • 注意:在 python3 中:该数字已降至 2。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-09
    • 2016-07-14
    • 2018-01-09
    • 1970-01-01
    • 2011-04-29
    相关资源
    最近更新 更多