空集的真值

我感兴趣的是Python集合的真值,比如{'a', 'b'}或者空set set() (它与空字典{}不一样)。 特别是,我想知道当bool(my_set)是否为False当且仅当set my_set为空时。

忽略原始(如数字)以及用户定义的types, https : //docs.python.org/3/library/stdtypes.html#truth说:

以下值被认为是错误的:

  • […]
  • 任何空序列,例如''()[]
  • 任何空的映射,例如{}
  • […]

所有其他值都被认为是正确的

根据https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range ,一个集合不是一个序列(它是无序的,它的元素没有索引等等。 ):

有三种基本的序列types:列表,元组和范围对象。

而且,根据https://docs.python.org/3/library/stdtypes.html#mapping-types-dict ,

目前只有一种标准的映射types,即字典

所以,就我所知,集合types不是一个可能是False的types。 但是,当我尝试, bool(set())评估为False

问题:

  • 这是一个文档问题,还是我得到错误?
  • 空集是唯一的真值是False集合吗?

在查看CPython的源代码后,我猜想这是一个文档错误,但是,它可能是依赖于实现的,因此在Python bug跟踪器上提出这个问题是一个很好的问题。

具体来说, object.c定义了一个项目的真值,如下所示:

 int PyObject_IsTrue(PyObject *v) { Py_ssize_t res; if (v == Py_True) return 1; if (v == Py_False) return 0; if (v == Py_None) return 0; else if (v->ob_type->tp_as_number != NULL && v->ob_type->tp_as_number->nb_bool != NULL) res = (*v->ob_type->tp_as_number->nb_bool)(v); else if (v->ob_type->tp_as_mapping != NULL && v->ob_type->tp_as_mapping->mp_length != NULL) res = (*v->ob_type->tp_as_mapping->mp_length)(v); else if (v->ob_type->tp_as_sequence != NULL && v->ob_type->tp_as_sequence->sq_length != NULL) res = (*v->ob_type->tp_as_sequence->sq_length)(v); else return 1; /* if it is negative, it should be either -1 or -2 */ return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int); } 

如果它不是一个布尔types,None,一个序列或者一个映射types,那么我们可以清楚地看到值是始终为真的,这将需要设置tp_as_sequence或tp_as_mapping。

幸运的是,看setobject.c显示集合实现tp_as_sequence,表明文档似乎是不正确的。

 PyTypeObject PySet_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "set", /* tp_name */ sizeof(PySetObject), /* tp_basicsize */ 0, /* tp_itemsize */ /* methods */ (destructor)set_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ (reprfunc)set_repr, /* tp_repr */ &set_as_number, /* tp_as_number */ &set_as_sequence, /* tp_as_sequence */ 0, /* tp_as_mapping */ /* ellipsed lines */ }; 

字典也实现了tp_as_sequence,所以它似乎虽然不是一个序列types,但它像序列一样,足以成为真理。

在我的观点中,文档应该澄清这一点:映射types或序列types将取决于它们的长度。

编辑由于user2357112正确指出, tp_as_sequencetp_as_mapping并不意味着types是一个序列或地图。 例如,dict实现了tp_as_sequence ,而list实现了tp_as_mapping

__bool__的文档声明这个方法被称为真值testing,如果它没有被定义,则__len__被评估:

被调用来实现真值testing和内置操作bool(); […]当这个方法没有被定义的时候, __len__()被调用,如果被定义的话,如果该对象的结果是非零的,那么该对象被认为是真的。 如果一个类既没有定义__len__()也没有定义__bool__() ,它的所有实例都被认为是真的。

这适用于任何Python对象。 我们可以看到set没有定义一个方法__bool__

 >>> set.__bool__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: type object 'set' has no attribute '__bool__' 

所以真相testing落在__len__

 >>> set.__len__ <slot wrapper '__len__' of 'set' objects> 

因此只有一个空集(零长度)被认为是错误的。

文件中的真值testing部分在这方面还不完整。

这部分文档写得不好,或者说维护得不好。 以下条款:

如果该类定义了__bool__()__len__()方法,则该方法返回整数零或布尔值False时,用户定义的类的实例。

真的适用于所有类,用户定义或不包括setdict ,甚至所有其他子句(所有这些定义__bool____len__ )中列出的types。 (在Python 2中,尽pipe没有__len__或者Python 2与__bool__等价,但是None错误,但是自从Python 3.3以来 ,这个exception已经消失了 。

我说维护得不好,因为这个版本至less在Python 1.4以后几乎没有变化,可能更早。 它已被更新为添加False和删除单独的int / longtypes,但不适用于types/类统一或集合的引入。

回到引用子句的时候,用户定义的类和内置types确实有不同的performance,我不认为内置types实际上有__bool____len__

Interesting Posts