为什么当[[]是[]“而'{}是{}'返回False时,'()是()'返回True? [英] Why does '() is ()' return True when '[] is []' and '{} is {}' return False?

查看:81
本文介绍了为什么当[[]是[]“而'{}是{}'返回False时,'()是()'返回True?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

据我所知,使用[], {}()实例化对象分别返回了list, dicttuple的新实例;具有新身份的新实例对象.

在我进行实际测试之前,这对我来说很清楚,我注意到() is ()实际上返回了True而不是预期的False:

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

如预期的那样,当使用 list() dict() 和<分别分别是href ="https://docs.python.org/3/library/functions.html#func-tuple" rel ="nofollow noreferrer"> tuple() :

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

我可以在 状态:

[...]例如,tuple('abc')返回('a', 'b', 'c'),而tuple([1, 2, 3])返回(1, 2, 3). 如果未提供任何参数,则构造函数将创建一个新的空元组().

这足以回答我的问题.

那么,为什么空元组具有相同的标识,而列表或字典等其他元组却没有呢?

解决方案

简而言之:

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

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

这很可能与以下事实有关:不变的对象(如元组)无法更改,因此保证在执行期间不会更改.考虑到frozenset() is frozenset()返回True时,这一点得到进一步巩固.例如()一个空的frozenset 被视为CPython 的实现.对于可变对象,此类保证没有到位,因此,没有动机来缓存其零元素实例(即,其内容可能会在身份不变的情况下发生变化).

注意: 这不是一个人应该依靠的东西,即一个人不应该将空元组视为单例.在文档中没有明确保证没有此类保证,因此应假定它与实现有关.


如何完成:

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

使用参数size == 0调用PyTuple_New时,请确保 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:

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

通过 do_call 调用的

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

从本质上讲,这可能是一个经常执行的操作,因此不要每次都构造一个空元组是有意义的.


进一步阅读:

更多的答案揭示了CPython具有不可变变量的缓存行为:

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.

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)

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)

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

[...] 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?

解决方案

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.

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

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).

Take note: This isn't something one should depend on, i.e one shouldn't consider empty tuples to be singletons. No such guarantees are explicitly made in the documentation so one should assume it is implementation dependent.


How it is done:

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.

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.. */

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.. */

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.


Further reading:

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.

这篇关于为什么当[[]是[]“而'{}是{}'返回False时,'()是()'返回True?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆