为什么当'[] is []'和'{} is {}'返回False时,'() is ()'返回True?

12 浏览
0 Comments

为什么当'[] is []'和'{} is {}'返回False时,'() is ()'返回True?

据我所知,使用[],{}()来实例化对象会返回一个新的list,dicttuple的实例;一个具有新标识的新实例对象。\n这对我来说很清楚,直到我实际测试并注意到() is ()实际上返回True,而不是预期的False:\n

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

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

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

\n在tuple()的文档中,我只找到了一条相关信息:\n

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

\n可以说,这并不足以回答我的问题。\n那么,为什么空元组具有相同的标识,而像列表或字典这样的其他对象却没有呢?

0
0 Comments

为什么 '() is ()' 返回 True ,而 '[] is []' 和 '{} is {}' 返回 False?

在Python中,每次使用 tuple() 或 () 时,Python内部会创建一个包含空元组的 C 列表。Python会返回存在于该 C 列表中的现有对象,而不是创建一个新的对象。这种机制对于 dict 或 list 对象是不存在的,相反,它们每次都会从头开始重新创建。

这很可能与不可变对象(如元组)无法更改的事实有关,并且因此在执行过程中保证不会更改。这在考虑到 frozenset() is frozenset() 返回 True 的情况下更加明显;与 () 一样,空的 frozenset 在 CPython 实现中被认为是单例的。对于可变对象,这种保证并不存在,因此没有动态地缓存它们的零元素实例(即使它们的内容保持不变,标识仍然相同)。

需要注意的是,不应该依赖于这一点,也就是说,不应该将空元组视为单例。文档中没有明确给出这样的保证,因此应该假设它是与实现相关的。

在大多数情况下,CPython 的实现编译时使用两个宏 PyTuple_MAXFREELIST 和 PyTuple_MAXSAVESIZE 设置为正整数。这些宏的正值会导致创建一个大小为 PyTuple_MAXSAVESIZE 的 tuple 对象数组。

当调用 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.. */
}

造成这一做法的另一个原因是函数调用会构造一个元组,用于保存要使用的位置参数。这可以在 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 为零,则会返回一个空元组。从本质上讲,这可能是一个频繁执行的操作,因此不需要每次都重新构造一个空元组。

更多阅读:

- 有关整数的另一个深入解析可以在此处找到。

- 有关字符串的一些答案可以在这里、这里和这里找到。

可以说这种行为不符合文档中的标准吗?因此是一个错误吗?

这绝对不被认为是一个错误;我的建议是不要依赖于此,因为在 Python 参考手册中没有关于它们是单例的参考。值得注意的单例对象(如 None、Ellipsis 等)都有明确的说明,而 () 或 frozenset() 则没有,因此可以安全地假设它们被视为实现细节,不应依赖于它们。

主要的问题是,由于元组是不可变的,从 Python 代码的角度来看,一个新的空元组与一个被回收的空元组在实际用途上是无法区分的。如果将空元组错误地用作标记对象,例如为了提供默认行为,则可能会编写出错误的代码,因此将“不用作标记”的空元组误认为是标记对象。应该使用适当的标记对象。

例如,pypy 实现是不同的。() is () 仍然返回 True,但 () is tuple() 或 tuple() is tuple() 返回 False。在 jython 中,所有三种形式都返回 False。

非常有趣,特别是 PyPy 的情况。这只是表明不同的实现根据其需求来实现不同的方式。很好的评论。

0