空集的真值

Pet*_*sen 41 python set truthiness

我有兴趣在Python的真值设置像{'a', 'b'},或空集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,

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

所以,据我所知,set类型不是一种类型False.但是,当我尝试时,bool(set())评估为False.

问题:

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

Ale*_*agh 30

在查看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);
}
Run Code Online (Sandbox Code Playgroud)

我们可以清楚地看到,如果值不是布尔类型,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 */
};
Run Code Online (Sandbox Code Playgroud)

Dicts也实现了tp_as_sequence,所以看起来虽然它不是序列类型,但它类似于序列,足以让它变得真实.

在我的观点中,文档应该澄清这一点:类似映射的类型或类似序列的类型将依赖于它们的长度.

编辑作为user2357112正确地指出,tp_as_sequencetp_as_mapping并不意味着类型是序列或映射.例如,dict实现tp_as_sequence和列表实现tp_as_mapping.

  • 稍微搁置一下 - Python语言实际上是由文档还是参考实现定义的? (7认同)
  • @peterthomassen:真的,几乎没有什么可以被视为规范.它不像C或C++,具有标准体和大多数官方标准.文档通常是不完整的,并且解释器的确切行为不是其他实现的规范. (6认同)
  • @shadow:Python级`__ bool__`对应于`v-> ob_type-> tp_as_number-> nb_bool`部分,它是C级对应部分.根据类型是用C还是Python编写,`__ bool__`或`nb_bool`将是另一个的包装器. (4认同)
  • 请注意,`tp_as_sequence`的存在并不意味着对象是一个序列,并且`tp_as_mapping`的存在并不意味着对象是一个映射.这些语言已经发展,因为这些成员的设计和命名. (2认同)
  • @AlexanderHuszagh我在Python bug跟踪器上提出了这个问题,请参阅https://bugs.python.org/issue30803 (2认同)

a_g*_*est 26

有关__bool__状态的文档说明此方法被调用以进行真值测试,如果未定义,则__len__评估:

被称为实施真值测试和内置操作bool(); [...]如果未定义此方法,__len__()则调用(如果已定义),并且如果对象的结果非零,则认为该对象为true.如果一个类既未定义也__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__'
Run Code Online (Sandbox Code Playgroud)

所以真相测试依赖于__len__:

>>> set.__len__
<slot wrapper '__len__' of 'set' objects>
Run Code Online (Sandbox Code Playgroud)

因此,只有空集(零长度)被视为假.

关于这方面,文档中真值测试的部分并不完整.


use*_*ica 18

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

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

真正适用于所有类,用户定义与否,包括set,dict甚至所有其他子句中列出的类型(所有这些都定义了__bool__或者__len__).(在Python 2中,None尽管没有__len__Python 2的等价物__bool__,但它是假的,但是自Python 3.3以来该例外已经消失了.)

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

回到引用的quoted子句时,用户定义的类和内置类型确实表现得不同,我认为内置类型实际上没有__bool____len__当时.

  • 只是提醒魔术方法可以依赖于实现:在Python 2中,`__ bool__`不存在,而`__nonzero__`提供相同的功能,我发现了很难的方法. (2认同)
  • @AlexanderHuszagh:是的.`__nonzero__`这个名字可追溯到Python有bool类型之前.那时,它返回0或1,并且该方法被认为是测试非军人而不是强迫bool. (2认同)