据我所知,使用[], {}或()实例化对象分别返回list, dict或的新实例tuple。具有 新标识 的新实例对象。
[], {}
()
list, dict
tuple
在我实际测试它之前,这对我来说是很清楚的,我注意到它() is ()实际上返回了True而不是预期的False:
() is ()
True
False
>>> () is (), [] is [], {} is {} (True, False, False)
如预期的那样,分别使用和创建对象时list(),也会表现出这种行为:dict()tuple()
list()
dict()
tuple()
>>> tuple() is tuple(), list() is list(), dict() is dict() (True, False, False)
我可以在状态文档中tuple()找到唯一相关的信息:
[…]例如,tuple('abc')return('a', 'b', 'c')和tuple([1, 2, 3])return (1, 2, 3)。 如果未提供任何参数,则构造函数将创建一个新的空元组()。
tuple('abc')
('a', 'b', 'c')
tuple([1, 2, 3])
(1, 2, 3)
可以说,这不足以回答我的问题。
那么,为什么空元组具有相同的标识,而其他元组(如列表或字典)却没有相同的标识呢?
Python在内部创建一个C元组对象列表,其第一个元素包含空元组。每次使用tuple()或时(),Python都会返回上述C列表中包含的现有对象,而不创建新对象。
C
相反,这种机制并不存在,dict或者 每次都从头开始重新创建的list对象都不存在。 __
dict
list
这很可能与以下事实有关:不变的对象(例如元组)无法更改,因此保证在执行期间不会更改。考虑到frozenset() is frozenset()回报True,这一点得到进一步巩固;像()空一样frozenset 被认为是实现的单例CPython。对于可变对象, 此类保证没有到位 ,因此也没有动机来缓存其零元素实例(即,其内容可能会在身份保持不变的情况下发生变化)。
frozenset() is frozenset()
frozenset
CPython
请注意: 这不是一个应该依靠的东西,即,一个人不应该将空元组视为单例。 在文档中没有明确保证此类保证,因此应假定它与实现有关。
在最常见的情况下,的实现CPython是用两个宏编译的,PyTuple_MAXFREELIST并PyTuple_MAXSAVESIZE设置为正整数。这些宏的正值会导致创建具有size的tuple对象数组PyTuple_MAXSAVESIZE。
PyTuple_MAXFREELIST
PyTuple_MAXSAVESIZE
当PyTuple_New使用参数调用时,请size == 0确保将新的空元组添加到列表(如果尚不存在):
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.. */
促使这样做的另一个原因是函数调用构造了一个元组来保存将要使用的位置参数的事实。可以在以下load_args函数中看到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为零,则将返回一个空的元组。
do_call
na
本质上,这可能是一个经常执行的操作,因此不要每次都构造一个空的元组是有意义的。