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:

  1. Check Boolean Slot (nb_bool): Does the object define the __bool__ method? If so, return its result.
  2. Check Length Slot (mp_length or sq_length): If __bool__ doesn’t exist, does the object define __len__? If so, return True for length > 0, and False for length 0.
  3. 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).

  1. CPython first calls a.__eq__(b).
  2. If a.__eq__ returns the special singleton NotImplemented, CPython gracefully falls back and calls b.__eq__(a).
  3. 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").

Display Options
Appearance
Text Size
100%