我对像{'a', 'b'}
这样的Python集合的真值,或者空集set()
(与空字典{}
不同)感兴趣。特别是,当且仅当集合bool(my_set)
为空时,我想知道False
是否为my_set
。
忽略基元(如数字)以及用户定义的类型,https://docs.python.org/3/library/stdtypes.html#truth说:
以下值被视为false:
- [...]
- 任何空序列,例如
''
,()
,[]
。- 任何空映射,例如
{}
。- [...]
所有其他值都被视为真实
根据https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range,一个集合不是一个序列(它是无序的,它的元素没有索引等):
有三种基本序列类型:列表,元组和范围对象。
而且,根据https://docs.python.org/3/library/stdtypes.html#mapping-types-dict,
目前只有一种标准的映射类型,字典。
因此,据我所知,集合类型不是False
的类型。但是,当我尝试时,bool(set())
评估为False
。
问题:
False
的唯一集合吗?答案 0 :(得分:29)
在查看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);
}
我们可以清楚地看到,如果值不是布尔类型,None,序列或映射类型,则值必须始终为true,这需要设置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 */
};
Dicts也实现了tp_as_sequence,所以看起来虽然它不是一个序列类型,但它类似于序列,足以让它变得真实。
在我的观点中,文档应该澄清这一点:类似映射的类型或类似序列的类型将依赖于它们的长度。
修改正如user2357112正确指出的那样,tp_as_sequence
和tp_as_mapping
并不意味着类型是序列或地图。例如,dict实现tp_as_sequence
,列表实现tp_as_mapping
。
答案 1 :(得分:24)
__bool__
的文档声明此方法被调用用于真值测试,如果未定义,则评估__len__
:
调用实现真值测试和内置操作bool(); [...]如果未定义此方法,则调用
__len__()
(如果已定义),如果对象的结果非零,则认为该对象为true。如果某个类既未定义__len__()
也未定义__bool__()
,则其所有实例都被视为true。
这适用于任何Python对象。我们可以看到set
没有定义方法__bool__
:
>>> set.__bool__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'set' has no attribute '__bool__'
所以真相测试可以追溯到__len__
:
>>> set.__len__
<slot wrapper '__len__' of 'set' objects>
因此,只有空集(零长度)被视为假。
文档中truth value testing的部分在这方面并不完整。
答案 2 :(得分:18)
这部分文档编写得很糟糕,或者说维护得很差。以下条款:
用户定义类的实例,如果类定义了
__bool__()
或__len__()
方法,则该方法返回整数零或bool值为False。
确实适用于所有类,用户定义与否,包括set
,dict
,甚至是所有其他子句中列出的类型(所有这些都定义了) __bool__
或__len__
)。 (在Python 2中,尽管没有None
或Python 2相当于__len__
,__bool__
仍为假,但该异常为gone since Python 3.3。)
我说维护得很差,因为这个部分至少从Python 1.4开始几乎没有变化,也许更早。它已被更新为添加False
并删除单独的int / long类型,但不是类型/类统一或集合的引入。
当写入quoted子句时,用户定义的类和内置类型确实表现得不同,我认为内置类型实际上没有__bool__
或__len__
时间。