I am interested in the truth value of Python sets like {'a', 'b'}
, or the empty set set()
(which is not the same as the empty dictionary {}
). In particular, I would like to know whether bool(my_set)
is False
if and only if the set my_set
is empty.
Ignoring primitive (such as numerals) as well as user-defined types, https://docs.python.org/3/library/stdtypes.html#truth says:
The following values are considered false:
- [...]
- any empty sequence, for example,
''
,()
,[]
.- any empty mapping, for example,
{}
.- [...]
All other values are considered true
According to https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range, a set is not a sequence (it is unordered, its elements do not have indices, etc.):
There are three basic sequence types: lists, tuples, and range objects.
And, according to https://docs.python.org/3/library/stdtypes.html#mapping-types-dict,
There is currently only one standard mapping type, the dictionary.
So, as far as I understand, the set type is not a type that can ever be False
. However, when I try, bool(set())
evaluates to False
.
Questions:
False
?After looking at the source code for CPython, I would guess this is a documentation error, however, it could be implementation dependent and therefore would be a good issue to raise on the Python bug tracker.
Specifically, object.c defines the truth value of an item as follows:
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);
}
We can clearly see that the value is value would be always true if it is not a boolean type, None, a sequence, or a mapping type, which would require tp_as_sequence or tp_as_mapping to be set.
Fortunately, looking at setobject.c shows that sets do implement tp_as_sequence, suggesting the documentation seems to be incorrect.
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 also implement tp_as_sequence, so it seems that although it is not a sequence type, it sequence-like, enough to be truthy.
In my opionion, the documentation should clarify this: mapping-like types, or sequence-like types will be truthy dependent on their length.
Edit As user2357112 correctly points out, tp_as_sequence
and tp_as_mapping
do not mean the type is a sequence or a map. For example, dict implements tp_as_sequence
, and list implements tp_as_mapping
.