42

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

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

以下值被认为是错误的:

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

所有其他值都被认为是真的

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

共有三种基本序列类型:列表、元组和范围对象。

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

目前只有一种标准映射类型,字典

所以,据我了解, set 类型不是永远可以是False. 但是,当我尝试时,bool(set())评估为False.

问题:

  • 这是文档问题,还是我出错了?
  • 空集是唯一真值为 的集False吗?
4

3 回答 3

29

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

具体来说,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、序列或映射类型,则 value is value 将始终为 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,所以看起来虽然不是sequence类型,但是是sequence-like,足够真实。

在我看来,文档应该澄清这一点:类似映射的类型或类似序列的类型将真实地取决于它们的长度。

编辑正如 user2357112 正确指出的那样,tp_as_sequence并不tp_as_mapping意味着类型是序列或映射。例如, dict implementstp_as_sequence和 list implements tp_as_mapping

于 2017-06-28T22:32:55.530 回答
27

的文档__bool__说明调用此方法进行真值测试,如果未定义,则__len__对其进行评估:

调用以实现真值测试和内置操作 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__'

所以真相测试依赖于__len__

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

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

在这方面,文档中的真值测试部分并不完整。

于 2017-06-28T22:48:00.407 回答
18

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

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

确实适用于所有类,无论是否用户定义,包括set, dict,甚至所有其他子句中列出的类型(所有这些子句都定义了__bool__or __len__)。(在 Python 2 中,None尽管没有 a__len__或 Python 2 的等效项__bool__,但该异常自 Python 3.3 以来就消失了。)

我说维护不善是因为这部分至少从Python 1.4开始几乎没有变化,也许更早。它已针对添加False和删除单独的 int/long 类型进行了更新,但不适用于类型/类统一或集合的引入。

回到编写引用子句的时候,用户定义的类和内置类型确实表现不同,而且我认为内置类型实际上没有__bool____len__当时没有。

于 2017-06-28T22:35:31.157 回答