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
?An empty set is a finite set since its cardinality is defined and is equal to 0.
Given statement is true i.e. an empty set is a subset of every set.
In symbols, we write X ∩ ∅ = ∅. The union of any set with the empty set is the set we started with. This is because there are no elements in the empty set, and so we are not adding any elements to the other set when we form the union. In symbols, we write X U ∅ = X.
In classical logic, with its intended semantics, the truth values are true (denoted by 1 or the verum ⊤), and untrue or false (denoted by 0 or the falsum ⊥); that is, classical logic is a two-valued logic. This set of two values is also called the Boolean domain.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With