【问题标题】:Why does '() is ()' return True when '[] is []' and '{} is {}' return False?为什么当'[] is []'和'{} is {}'返回False时'()是()'返回True?
【发布时间】:2016-11-14 16:39:39
【问题描述】:

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

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

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

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

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

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

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

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

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

【问题讨论】:

标签: python python-3.x tuples identity python-internals


【解决方案1】:

简而言之:

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

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

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

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


它是如何完成的:

在最常见的情况下,CPython 的实现是用两个宏 PyTuple_MAXFREELISTPyTuple_MAXSAVESIZE 设置为正整数来编译的。这些宏的正值导致创建大小为PyTuple_MAXSAVESIZEarray 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 中的元组而不是一个新实例:

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 的数量为零,则将返回一个空元组。

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


延伸阅读:

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

  • 对于整数,可以在源代码中找到另一个答案here
  • 对于字符串,可以在hereherehere 找到一些答案。

【讨论】:

  • @vijrox 绝对不被视为错误;我的建议是不要依赖于此,因为在Python Reference Manual 中没有提到它们是单身人士。像NoneEllipsis 和其他值得注意的单例被明确提及,()frozenset() 不是因此可以安全地假设它们被认为是不应该依赖的实现细节。
  • 对 - 但是我指的是您在问题中指出的文档部分,即“如果没有给出参数,则构造函数会创建一个新的空元组 ()。”这种行为似乎(对我来说)与文档的那部分完全矛盾。
  • @vijrox 啊,是的,这也让我摸不着头脑。也许这是一个不试图使参考手册过于混乱的情况;也许这样写是为了不强制其他实现具有该限制。我真的不知道,但是我很困惑。
  • @vijrox 不,这只是实现细节。空元组的行为 100% 准确于语言规范。语言规范没有说空元组不能是单例。与语言规范没有说必须缓存小整数相同(然而,in CPython they are)。不保证 () is () 为 True,与不保证 3 is 3 为 True 相同,但如果确实如此,它不会破坏元组类与其用户之间的任何约定。
  • 例如,pypy 实现是不同的。 () is () 仍然返回 True,但 () is tuple()tuple() is tuple() 返回 False。而在 jython 中,所有三种形式都返回 False。
猜你喜欢
  • 1970-01-01
  • 2018-11-29
  • 2022-01-02
  • 2011-05-22
  • 2020-05-19
  • 2015-03-22
  • 2021-07-02
  • 1970-01-01
  • 2016-03-29
相关资源
最近更新 更多