Подтвердить что ты не робот

Значение истины пустого набора

Меня интересует значение истинности наборов 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,

В настоящее время существует только один стандартный тип сопоставления, словарь.

Итак, насколько я понимаю, тип набора не является типом, который может быть False. Однако, когда я пытаюсь, bool(set()) оценивается как False.

Вопросы:

  • Является ли это проблемой документации или я что-то не так понял?
  • Является ли пустым множеством единственное множество, значение истинности которого False?
4b9b3361

Ответ 1

Посмотрев исходный код для 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);
}

Мы можем ясно видеть, что значение value будет всегда истинным, если оно не является логическим типом, None, последовательностью или типом отображения, для чего требуется установить 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, а list реализует tp_as_mapping.

Ответ 2

В документации для __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>

Следовательно, только пустое множество (нуль-длина) считается ложным.

Часть для проверки истины в документации не является полной в отношении этого аспекта.

Ответ 3

Эта часть документов плохо написана или, вернее, плохо поддерживается. Следующее предложение:

экземпляров пользовательских классов, если класс определяет метод __bool__() или __len__(), когда этот метод возвращает целое число 0 или значение bool False.

действительно применяется ко всем классам, определяемым пользователем или нет, включая set, dict и даже типы, перечисленные во всех других предложениях (все из которых определяют либо __bool__, либо __len__). (В Python 2, None является ложным, несмотря на отсутствие эквивалента __len__ или Python 2 __bool__, но это исключение прошло с Python 3.3.)

Я утверждаю, что плохо поддерживается, потому что этот раздел почти не изменился, поскольку Python 1.4 и, возможно, раньше. Он был обновлен для добавления False и удаления отдельных типов int/long, но не для унификации типа/класса или введения наборов.

Назад, когда была написана цитата, пользовательские классы и встроенные типы действительно вели себя по-разному, и я не думаю, что встроенные типы на самом деле имели __bool__ или __len__ в то время.