空集的真值

时间:2017-06-28 22:13:21

标签: python set truthiness

我对像{'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的唯一集合吗?

3 个答案:

答案 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_sequencetp_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。

确实适用于所有类,用户定义与否,包括setdict,甚至是所有其他子句中列出的类型(所有这些都定义了) __bool____len__)。 (在Python 2中,尽管没有None或Python 2相当于__len____bool__仍为假,但该异常为gone since Python 3.3。)

我说维护得很差,因为这个部分至少从Python 1.4开始几乎没有变化,也许更早。它已被更新为添加False并删除单独的int / long类型,但不是类型/类统一或集合的引入。

当写入quoted子句时,用户定义的类和内置类型确实表现得不同,我认为内置类型实际上没有__bool____len__时间。