Значение истины пустого набора
Меня интересует значение истинности наборов 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
?
Ответы
Ответ 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__
в то время.