当'[]是[]'并且'{}是{}'返回False时,为什么'()是()'返回True?

Jim*_*ard 47 python identity tuples python-3.x python-internals

根据我的意识,使用[], {}()实例化对象会分别返回一个list, dict或新的实例tuple; 具有新标识的新实例对象.

这对我来说非常清楚,直到我实际测试它并且我注意到() is ()实际返回True而不是预期False:

>>> () is (), [] is [], {} is {}
(True, False, False)
Run Code Online (Sandbox Code Playgroud)

正如所料,与创建对象时,这种行为还表现list(),dict()tuple()分别为:

>>> tuple() is tuple(), list() is list(), dict() is dict()
(True, False, False)
Run Code Online (Sandbox Code Playgroud)

我可以在状态文档中tuple()找到的唯一相关信息:

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

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

那么,为什么空元组具有相同的身份,而其他像列表或词典不具有相同的身份?

Jim*_*ard 53

简而言之:

Python在内部创建了一个C元组对象列表,其第一个元素包含空元组.每次tuple()()使用,Python会返回包含在上述现有的对象C列表,而不是创建一个新的.

这种机制不存在,dict或者list相反,每次都是从头开始重新创建的对象.

这很可能与以下事实有关:不可变对象(如元组)无法更改,因此保证在执行期间不会更改.在考虑frozenset() is frozenset()回报时,这进一步巩固True; 像()空一样frozenset 被认为是执行中的单例CPython.对于可变对象,这种保证不到位,因此,没有动机缓存它们的零元素实例(即它们的内容可能随着身份保持不变而改变).

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


怎么做:

在最常见的情况下,实现CPython是使用两个宏编译PyTuple_MAXFREELISTPyTuple_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 */
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

do_call在同一个文件中调用via .如果参数的数量na为零,则将返回空元组.

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


进一步阅读:

更多的答案揭示CPython了不可变的缓存行为:

  • @vijrox不,这只是实现细节.空元组表现出100%准确的语言规范.语言规范没有说,空元组不能是单例.与语言规范相同_不要说_必须缓存小整数(然而,[在CPython中它们是](http://stackoverflow.com/questions/306313/is-operator-behaves-unexpectedly-with-integers)) .它不能保证`()是()`是真的,同样不能保证`3是3`是真的,但如果确实如此,它不会破坏元组类和它们用户之间的任何类型的契约. (9认同)
  • @vijrox绝对不算错误; 我的建议是不依赖于此,因为在[Python参考手册](https://docs.python.org/3/reference/index.html#reference-index)中没有关于它们是单例的参考.明确提到了诸如"无","省略号"等明显的单例,`()`或`frozenset()`因此不能安全地假设它们被认为是一个不应该依赖的实现细节. (4认同)
  • @vijrox啊是的,这确实让我刮伤了我的脑袋.也许这是一个不试图使参考手册太混乱的情况; 也许它的编写方式是不强制其他实现具有该限制.我真的不知道但是,我感到困惑. (3认同)
  • 例如,pypy实现是不同的.`()是()`仍然返回True,但是`()是tuple()`或`tuple()是tuple()`返回False.在jython中,所有三种形式都返回False. (3认同)
  • 是的-但是,我指的是您在问题中指出的文档部分,即“如果未给出任何参数,则构造函数将创建一个新的空元组()。” 在我看来,这种行为与文档的那一部分完全矛盾。 (2认同)
  • 主要的一点是,由于元组是不可变的,从 Python 代码的角度来看,新的空元组在实际应用中与回收的空元组没有区别。例如,如果尝试使用空元组作为哨兵对象以提供默认行为,则该行为可能会导致编写错误代码,从而将“不意味着作为哨兵”的空元组误认为是该对象。只需使用适当的哨兵即可。 (2认同)