为什么'()is()'在'为'时返回True,'{}是{'是'返回False?

从我已经知道,使用[], {}()来实例化对象返回一个新的list, dicttuple实例; 一个具有新身份的新实例对象。

这是相当清楚的,直到我真正testing它,我注意到() is ()实际上返回True而不是预期的False

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

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

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

我可以在tuple()的文档中find唯一相关的信息:

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

我只想说,这还不足以回答我的问题。

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

简而言之:

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

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

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

注意: 这不是应该依赖的东西,也就是说不应该把空元组看作是单身。 在文档中没有明确的这样的保证,所以应该假设它是依赖于实现的。


它是如何完成的:

在最常见的情况下, CPython的实现是用两个macrosPyTuple_MAXFREELISTPyTuple_MAXSAVESIZE设置为正整数编译的。 这些macros的正值导致创build一个大小为PyTuple_MAXSAVESIZEtuple对象数组 。

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.cload_args函数中ceval.c

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

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

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


进一步阅读:

多一些答案揭示了CPython的caching行为与不可变:

  • 对于整数,可以在这里find另一个在源代码中挖掘的答案。
  • 对于string, 这里和这里可以find一些答案。