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

问题描述

据我所知,使用 []、{}() 实例化对象会返回 list、dict<的新实例/code> 或 tuple 分别;具有新身份的新实例对象.

From what I've been aware of, using [], {} or () to instantiate objects returns a new instance of list, dict or tuple respectively; a new instance object with a new identity.

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

This was pretty clear to me until I actually tested it and I noticed that () is () actually returns True instead of the expected False:

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

正如预期的那样,当使用 <代码>list(), dict()tuple() 分别为:

as expected, this behavior is also manifested when creating objects with list(), dict() and tuple() respectively:

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

我可以在 tuple() 状态:

The only relevant piece of information I could find in the docs for tuple() states:

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

[...] 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, ().

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

Suffice to say, this isn't sufficient for answering my question.

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

So, why do empty tuples have the same identity whilst others like lists or dictionaries do not?


解决方案

总之:

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

In short:

Python internally creates a C list of tuple objects whose first element contains the empty tuple. Every time tuple() or () is used, Python will return the existing object contained in the aforementioned C list and not create a new one.

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

Such mechanism does not exist for dict or list objects which are, on the contrary, recreated from scratch every time.

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

This is most likely related to the fact that immutable objects (like tuples) cannot be altered and, as such, are guaranteed to not change during execution. This is further solidified when considering that frozenset() is frozenset() returns True; like () an empty frozenset is considered an singleton in the implementation of CPython. With mutable objects, such guarantees are not in place and, as such, there's no incentive to cache their zero element instances (i.e their contents could change with the identity remaining the same).

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

在最常见的情况下,CPython 的实现是用两个宏PyTuple_MAXFREELISTPyTuple_MAXSAVESIZE 设置为正整数.这些宏的正值导致创建 数组tuple 对象,大小为 PyTuple_MAXSAVESIZE.

In the most common case, the implementation of CPython is compiled with two macros 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 with size PyTuple_MAXSAVESIZE.

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

When PyTuple_New is called with the parameter size == 0 it makes sure to add a new empty tuple to the list if it doesn't already exist:

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

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

Then, if a new empty tuple is requested, the one that is located in the first position of this list is going to get returned instead of a new instance:

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

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

One additional reason causing an incentive to do this is the fact that function calls construct a tuple to hold the positional arguments that are going to be used. This can be seen in the load_args function in ceval.c:

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

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

which is called via do_call in the same file. If the number of arguments na is zero, an empty tuple is going to be returned.

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

In essence, this might be an operation that's performed frequently so it makes sense to not reconstruct an empty tuple every single time.

更多答案阐明了 CPython 的不可变缓存行为:

A couple more answers shed light on CPython's caching behaviour with immutables:

  • 对于整数,可以在此处找到另一个挖掘源代码的答案.
  • 对于字符串,可以在这里找到一些答案,这里和这里.
  • For integers, another answer that digs in the source can be found here.
  • For strings, a handful of answers can be found here, here and here.

相关文章