Expressions, Operators & Truthiness Protocols
An expression computes a value; a statement directs the program’s execution flow. While operators look like standard mathematical syntax, in CPython they are syntactic sugar that resolve into sophisticated C-level slot lookups, dunder method invocations, and optimized bytecode instructions.
This chapter breaks down bytecode short-circuiting, the operator method resolution order (MRO), and how the VM decides what makes an object “Truthy” or “Falsey”.
1. Truthiness Protocol (nb_bool vs mp_length)
Every object in Python can be evaluated in a boolean context (if obj:). Unlike strictly typed languages that require an explicit boolean expression, CPython uses the fast-path PyObject_IsTrue() C function to determine truthiness.
The resolution sequence is:
- Check Boolean Slot (
nb_bool): Does the object define the__bool__method? If so, return its result. - Check Length Slot (
mp_lengthorsq_length): If__bool__doesn’t exist, does the object define__len__? If so, returnTruefor length > 0, andFalsefor length 0. - Default to True: If neither is defined, CPython defaults to
True.
This is why empty lists [], empty dicts {}, and 0 are Falsey, while custom objects are Truthy by default unless programmed otherwise.
2. Operator Method Resolution & Fallbacks
When CPython executes a comparison like a == b, it relies on the rich comparison protocol (PyObject_RichCompare).
- CPython first calls
a.__eq__(b). - If
a.__eq__returns the special singletonNotImplemented, CPython gracefully falls back and callsb.__eq__(a). - If both return
NotImplemented, CPython falls back to an identity check (a is b) for equality comparisons.
This bi-directional protocol allows newer classes to compare themselves against older classes without patching the old class.
3. Visual Mental Model: Bytecode Short-Circuiting
Logical operators (and, or) do not compute boolean values; they return one of the evaluated objects. To execute this efficiently, the CPython compiler optimizes them using short-circuiting bytecode jump instructions (JUMP_IF_FALSE_OR_POP and JUMP_IF_TRUE_OR_POP).
Evaluating: result = A() or B()
[ Stack: Push A() ]
|
v
[ Opcode: JUMP_IF_TRUE_OR_POP ]
|
+---> If A() is Truthy: Jump to end of expression! (B is never executed)
| Leaves A() on the top of the stack as the final result.
|
+---> If A() is Falsey: Pop A() off the stack.
|
v
[ Stack: Push B() ]
|
v
[ Return B() as the final result (regardless of truthiness) ]Because or and and return the actual object (not a boolean), they are often used for quick fallback assignments (token = user_token or "default").