空集的真值

Truth value of empty set

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

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

The following values are considered false:

  • [...]
  • any empty sequence, for example, '', (), [].
  • any empty mapping, for example, {}.
  • [...]

All other values are considered true

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

There are three basic sequence types: lists, tuples, and range objects.

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

There is currently only one standard mapping type, the dictionary.

因此,据我了解,set 类型不是可以成为 False 的类型。但是,当我尝试时,bool(set()) 的计算结果为 False.

问题:

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

具体来说,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,足够真实了。

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

Edit 正如 user2357112 正确指出的那样,tp_as_sequencetp_as_mapping 并不意味着类型是序列或映射。比如dict实现了tp_as_sequence,list实现了tp_as_mapping.

这部分文档写得不好,或者更确切地说,维护得不好。以下子句:

instances of user-defined classes, if the class defines a __bool__() or __len__() method, when that method returns the integer zero or bool value False.

真正适用于所有 类,用户自定义与否,包括setdict,甚至是中列出的类型所有其他子句(所有这些子句都定义了 __bool____len__)。 (在 Python 2 中,尽管没有 __len__ 或 Python 2 的 __bool__ 等价物,但 None 是假的,但那个例外是 gone since Python 3.3。 )

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

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

__bool__ states that this method is called for truth value testing and if it is not defined then __len__ 的文档被评估:

Called to implement truth value testing and the built-in operation bool(); [...] When this method is not defined, __len__() is called, if it is defined, and the object is considered true if its result is nonzero. If a class defines neither __len__() nor __bool__(), all its instances are considered 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 的部分在这方面并不完整。