空集的真值
我感兴趣的是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_sequence
和tp_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时,用户定义的类的实例。
真的适用于所有类,用户定义或不包括set
, dict
,甚至所有其他子句(所有这些定义__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__
。