为什么当“[]是[]”而“{}是{}”返回False时,“()是()”返回True?

问题描述 投票:0回答:1

据我所知,使用

[]
{}
()
实例化对象分别返回
list
dict
tuple
的新实例;具有新身份的新实例对象。

这对我来说非常清楚,直到我实际测试它并且我注意到

() is ()
实际上返回
True
而不是预期的
False

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

正如预期的那样,当分别使用

list()
dict()
tuple()
创建对象时,也会表现出这种行为:

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

我可以在

tuple()
的文档中找到的唯一相关信息指出:

[...] 例如,

tuple('abc')
返回
('a', 'b', 'c')
tuple([1, 2, 3])
返回
(1, 2, 3)
如果没有给出参数,构造函数将创建一个新的空元组,
()

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

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

python tuples identity python-internals
1个回答
54
投票

简而言之:

Python 在内部创建一个

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

对于

dict
list
对象来说,不存在这种机制,相反,每次都从头开始重新创建

这很可能与不可变对象(如元组)无法更改这一事实有关,因此保证在执行期间不会更改。当考虑到

frozenset() is frozenset()
返回
True
时,这一点得到进一步巩固;就像
()
一个空的
frozenset
CPython
的实现中被认为是单例。对于可变对象,这样的保证不存在,因此,没有动力缓存它们的零元素实例(即它们的内容可能会随着身份保持不变而改变)。

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


如何完成:

在最常见的情况下,

CPython
的实现是用两个设置为正整数的宏
PyTuple_MAXFREELIST
PyTuple_MAXSAVESIZE
来编译的。这些宏的正值会导致创建大小为 tuple
PyTuple_MAXSAVESIZE
 对象
数组。

当使用参数

PyTuple_New
调用
size == 0
时,它会确保 将一个新的空元组添加到列表中(如果它尚不存在):

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

然后,如果请求一个新的空元组,则将返回位于该列表的第一个位置的元组,而不是新实例:

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

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

中的
ceval.c
函数中看到:

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

在同一文件中通过

do_call
调用。如果参数
na
的数量为零,则将返回一个空元组。

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


进一步阅读:

还有几个答案阐明了

CPython
的不可变缓存行为:

  • 对于整数,可以在here找到另一个在源代码中挖掘的答案。
  • 对于字符串,可以在这里这里这里找到一些答案。
© www.soinside.com 2019 - 2024. All rights reserved.