How CPython Implements bool in C
bool as a subclass of int, the immortal True/False singletons, tp_new, truthiness, and bitwise overrides traced through the CPython 3.15 source.
Python’s bool is unusual: it is a real type, but there are exactly two
instances, True and False, and they are also the integers 1 and 0.
In CPython that is implemented in Objects/boolobject.c by making
PyBool_Type a subclass of PyLong_Type and storing the two values as
static _longobject structs. This article traces that implementation
through the 3.15 source.
The type is a subclass of int
bool is not a separate representation. It reuses PyLongObject’s layout
and inherits almost everything from int:
// Objects/boolobject.c
PyTypeObject PyBool_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"bool",
offsetof(struct _longobject, long_value.ob_digit), /* tp_basicsize */
sizeof(digit), /* tp_itemsize */
bool_dealloc, /* tp_dealloc */
...
&bool_as_number, /* tp_as_number */
...
&PyLong_Type, /* tp_base */
...
bool_new, /* tp_new */
.tp_vectorcall = bool_vectorcall,
};Two details matter. First, tp_base is &PyLong_Type, so bool is a real
subclass of int: isinstance(True, int) is true and all of int’s
methods are inherited (see below). Second, tp_flags is just
Py_TPFLAGS_DEFAULT – there is no Py_TPFLAGS_BASETYPE, so
bool cannot be subclassed in Python:
>>> class MyBool(bool): pass
TypeError: type 'bool' is not an acceptable base typePyBool_Check is an exact type check, not a subclass check, which is why
the bitwise overrides below can safely assume “this is exactly a bool”:
// Include/boolobject.h
#define PyBool_Check(x) Py_IS_TYPE((x), &PyBool_Type)The two singletons
True and False are not allocated at runtime. They are static C structs
of type _longobject (the same struct int uses), initialized at compile
time:
// Objects/boolobject.c
struct _longobject _Py_FalseStruct = {
PyObject_HEAD_INIT(&PyBool_Type)
{ .lv_tag = _PyLong_FALSE_TAG,
{ 0 }
}
};
struct _longobject _Py_TrueStruct = {
PyObject_HEAD_INIT(&PyBool_Type)
{ .lv_tag = _PyLong_TRUE_TAG,
{ 1 }
}
};The tags are defined in terms of the ordinary long tag machinery:
// Include/internal/pycore_long.h
#define _PyLong_FALSE_TAG TAG_FROM_SIGN_AND_SIZE(0, 0)
#define _PyLong_TRUE_TAG TAG_FROM_SIGN_AND_SIZE(1, 1)So False is “zero digits, zero value” and True is “one digit, value 1” –
exactly the same encoding an int would use. That is why bool needs no
special conversion to behave as an int: True + True == 2, True * 10 == 10, and int(True) is True.
The public macros resolve to these structs (or, in the limited API, to immortal constants):
// Include/boolobject.h
#define Py_False _PyObject_CAST(&_Py_FalseStruct)
#define Py_True _PyObject_CAST(&_Py_TrueStruct)Because they are statically allocated, they go through PyObject_HEAD_INIT,
which marks them immortal (PEP 683). They also appear in the interpreter’s
constant table (Py_CONSTANT_FALSE / Py_CONSTANT_TRUE), whose entries are
all immortal. The bool_dealloc is a belt-and-braces guard in case a refcount
ever reaches zero: it simply re-immortalizes the object instead of freeing it.
// Objects/boolobject.c
static void
bool_dealloc(PyObject *boolean)
{
_Py_SetImmortal(boolean);
}PyBool_FromLong is the single factory. It never allocates; it just picks
the right singleton:
// Objects/boolobject.c
PyObject *PyBool_FromLong(long ok)
{
return ok ? Py_True : Py_False;
}tp_new: always a singleton
bool() (and bool(x)) is implemented by bool_new / bool_vectorcall.
The rule is simple: unpack at most one argument, test its truthiness, and
return Py_True or Py_False:
// Objects/boolobject.c
static PyObject *
bool_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *x = Py_False;
long ok;
if (!_PyArg_NoKeywords("bool", kwds))
return NULL;
if (!PyArg_UnpackTuple(args, "bool", 0, 1, &x))
return NULL;
ok = PyObject_IsTrue(x);
if (ok < 0)
return NULL;
return PyBool_FromLong(ok);
}bool_vectorcall is the faster calling convention used for bool(x); it
does the same thing, defaulting to False when called with no arguments. Note
that type is accepted but never used to construct a new instance – there is
no way to create a third bool, which is what makes True/False the only
two instances the docstring promises.
Truthiness
bool_new delegates truthiness to PyObject_IsTrue in Objects/object.c.
That function has fast paths for the three singletons and otherwise consults
the type’s nb_bool, then mapping length, then sequence length, defaulting
to true:
// Objects/object.c
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 (Py_TYPE(v)->tp_as_number != NULL &&
Py_TYPE(v)->tp_as_number->nb_bool != NULL)
res = (*Py_TYPE(v)->tp_as_number->nb_bool)(v);
else if (Py_TYPE(v)->tp_as_mapping != NULL &&
Py_TYPE(v)->tp_as_mapping->mp_length != NULL)
res = (*Py_TYPE(v)->tp_as_mapping->mp_length)(v);
else if (Py_TYPE(v)->tp_as_sequence != NULL &&
Py_TYPE(v)->tp_as_sequence->sq_length != NULL)
res = (*Py_TYPE(v)->tp_as_sequence->sq_length)(v);
else
return 1;
return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int);
}This is also what if x:, not x, and the and/or operators use, so
bool(x) and truthiness agree by construction. Note bool itself has
nb_bool == 0 (left NULL so it is inherited from int); an int’s
nb_bool is effectively “nonzero”, so bool(True) works through the
Py_True fast path before ever reaching that.
bool as int
Because PyBool_Type is a subclass of PyLong_Type, almost all arithmetic
is inherited. bool_as_number only fills in a handful of slots; the rest are
copied from int by PyType_Ready (COPYNUM):
// Objects/boolobject.c
static PyNumberMethods bool_as_number = {
0, /* nb_add */ // inherited from int
...
bool_invert, /* nb_invert */
...
bool_and, /* nb_and */
bool_xor, /* nb_xor */
bool_or, /* nb_or */
...
};So True + True == 2 (an int), True << 3 == 8, and so on, all use
int’s implementations. Only the bitwise logical operators are overridden,
and only so that &, |, ^ on two bools return a bool instead of an int:
// Objects/boolobject.c
static PyObject *
bool_and(PyObject *a, PyObject *b)
{
if (!PyBool_Check(a) || !PyBool_Check(b))
return PyLong_Type.tp_as_number->nb_and(a, b);
return PyBool_FromLong((a == Py_True) & (b == Py_True));
}
static PyObject *
bool_or(PyObject *a, PyObject *b)
{
if (!PyBool_Check(a) || !PyBool_Check(b))
return PyLong_Type.tp_as_number->nb_or(a, b);
return PyBool_FromLong((a == Py_True) | (b == Py_True));
}
static PyObject *
bool_xor(PyObject *a, PyObject *b)
{
if (!PyBool_Check(a) || !PyBool_Check(b))
return PyLong_Type.tp_as_number->nb_xor(a, b);
return PyBool_FromLong((a == Py_True) ^ (b == Py_True));
}If either operand is not exactly a bool, the call falls through to int’s
bitwise operation, which returns an int. ~ is special: it is deprecated
on bool (it returns the underlying int’s bitwise inversion, e.g. ~True == -2) and emits a DeprecationWarning, and will be removed in 3.16.
bool_repr returns the interned identifiers, so repr(True) == "True":
// Objects/boolobject.c
static PyObject *
bool_repr(PyObject *self)
{
return self == Py_True ? &_Py_ID(True) : &_Py_ID(False);
}Hashing and comparison are inherited from int: hash(True) == 1,
True == 1 is True, and True is 1 is False – the usual
identity-vs-equality distinction, since 1 is a separate (possibly cached)
int object.
Memory layout summary
There is no separate allocation for bools. The two singletons are static
_longobjects whose lv_tag and single digit are the values 0 and 1:
_Py_FalseStruct _Py_TrueStruct
+---------------------------+ +---------------------------+
| ob_refcnt (immortal) | | ob_refcnt (immortal) |
+---------------------------+ +---------------------------+
| *ob_type (&PyBool_Type) | | *ob_type (&PyBool_Type) |
+---------------------------+ +---------------------------+
| lv_tag (FALSE_TAG: 0) | | lv_tag (TRUE_TAG: 1<<3) |
+---------------------------+ +---------------------------+
| ob_digit[0] = 0 | | ob_digit[0] = 1 |
+---------------------------+ +---------------------------+PyBool_Type.tp_basicsize is offsetof(struct _longobject, long_value.ob_digit)
and tp_itemsize is sizeof(digit), identical to int, so even the
memory layout is shared with PyLongObject.
Key takeaways
boolis a subclass ofint(PyBool_Type.tp_base == &PyLong_Type) with noPy_TPFLAGS_BASETYPE, so it cannot be subclassed.TrueandFalseare two immortal static_longobjects whose tags and digits are the integers1and0;PyBool_FromLongonly selects between them, so no bool is ever allocated.bool(x)(bool_new/bool_vectorcall) tests truthiness viaPyObject_IsTrueand returns a singleton.- Truthiness fast-paths
True/False/None, thennb_bool, mapping length, sequence length, defaulting to true. - All arithmetic is inherited from
int; only&,|,^(and a deprecated~) are overridden so bool op bool returns bool.
Further reading
Objects/boolobject.c– type object, singletons,tp_new, bitwise ops.Include/boolobject.h–Py_True/Py_Falsemacros andPyBool_Check.Objects/object.c–PyObject_IsTrue.Include/internal/pycore_long.h–_PyLong_FALSE_TAG/_PyLong_TRUE_TAG.Objects/longobject.c– the inheritedintimplementation.