为什么“() 是 ()”return True 而“[] 是 []”且“{} 是 {}”return False?

Why does '() is ()' return True when '[] is []' and '{} is {}' return False?

据我所知,使用 [], {}() 实例化对象 returns 分别是 list, dicttuple 的新实例;具有 新身份 的新实例对象。

这对我来说很清楚,直到我实际测试它并且我注意到 () is () 实际上 returns True 而不是预期的 False:

>>> () is (), [] is [], {} is {}
(True, False, False)

正如预期的那样,在分别使用 list(), dict() and tuple() 创建对象时也会表现出这种行为:

>>> tuple() is tuple(), list() is list(), dict() is dict()
(True, False, False)

我在 the docs for tuple() 中找到的唯一相关信息是:

[...] For example, tuple('abc') returns ('a', 'b', 'c') and tuple([1, 2, 3]) returns (1, 2, 3). If no argument is given, the constructor creates a new empty tuple, ().

可以这么说,这不足以回答我的问题。

那么,为什么空元组具有相同的身份,而列表或字典等其他元组却没有?

简而言之:

Python 在内部创建一个 C 元组对象列表,其第一个元素包含空元组。每次使用 tuple()() 时,Python 将 return 包含在上述 C 列表中的现有对象,而不创建新对象。

dictlist 对象不存在这种机制,相反,它们 每次都从头开始重新创建 .

这很可能与不可变对象(如元组)无法更改这一事实有关,因此,保证在执行期间不会更改。当考虑到 frozenset() is frozenset() returns True 时,这一点得到进一步巩固;像 () 一个空的 frozenset is considered an singleton in the implementation of CPython。对于可变对象,此类保证不存在,因此,没有动机缓存它们的零元素实例(即它们的内容可能会随着标识保持不变而改变)。

注意: 这不是我们应该依赖的东西,即我们不应该将空元组视为单例。文档中没有明确做出此类保证,因此应该假定它取决于实现。


如何完成:

在最常见的情况下,CPython 的实现是用两个大小为 PyTuple_MAXSAVESIZE 的宏 PyTuple_MAXFREELIST and PyTuple_MAXSAVESIZE set to positive integers. The positive value for these macros results in the creation of an array of tuple objects 编译的。

当使用参数 size == 0 调用 PyTuple_New 时,如果它不存在,它确保 add a new empty tuple 到列表:

if (size == 0) {
    free_list[0] = op;
    ++numfree[0];
    Py_INCREF(op);          /* extra INCREF so that this is never freed */
}

然后,如果请求一个新的空元组,位于 first position of this list 中的元组将被 returned 而不是新实例:

if (size == 0 && free_list[0]) {
    op = free_list[0];
    Py_INCREF(op);
    /* rest snipped for brevity.. */

促使这样做的另一个原因是函数调用构造一个元组来保存将要使用的位置参数。这可以在ceval.c中的load_args函数中看到:

static PyObject *
load_args(PyObject ***pp_stack, int na)
{
    PyObject *args = PyTuple_New(na);
    /* rest snipped for brevity.. */

在同一文件中通过 do_call 调用。如果参数的数量 na 为零,一个空元组将被 returned。

本质上,这可能是一个经常执行的操作,因此不要每次都重建一个空元组是有意义的。


延伸阅读:

另外几个答案阐明了 CPython 的不可变缓存行为:

  • 对于整数,可以找到另一个深入挖掘源码的答案here
  • 对于字符串,可以找到一些答案 here, and here