为什么'()is()'在'为'时返回True,'{}是{'是'返回False?
从我已经知道,使用[], {}
或()
来实例化对象返回一个新的list, dict
或tuple
实例; 一个具有新身份的新实例对象。
这是相当清楚的,直到我真正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一个新对象。
dict
或list
对象不存在这种机制,相反, 每次从头开始重新创build 。
这很可能与不可变对象(如元组)不可更改的事实有关,因此在执行期间保证不会更改。 当考虑到frozenset() is frozenset()
返回True
时,这是进一步固化; like ()
一个空的frozenset
在CPython
的实现中被认为是一个单例 。 对于可变对象, 这样的保证不存在 ,因此,没有动机来caching它们的零元素实例(即,它们的内容可能随着身份保持不变而改变)。
注意: 这不是应该依赖的东西,也就是说不应该把空元组看作是单身。 在文档中没有明确的这样的保证,所以应该假设它是依赖于实现的。
它是如何完成的:
在最常见的情况下, CPython
的实现是用两个macrosPyTuple_MAXFREELIST
和PyTuple_MAXSAVESIZE
设置为正整数编译的。 这些macros的正值导致创build一个大小为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
函数中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一些答案。