Unravelling boolean operations

As part of my series on Python's syntactic sugar, I am going to cover boolean operations: a or b and a and b.

The semantics

A key thing to know about or and and is that they short-circuit. What that means is that if just the first argument is enough to know the outcome of the expression then the second argument is never evaluated. Compare that to something like addition where both the left-hand side and right-hand side of + are evaluated no matter what.

In the case of a or b, if a is true then a is the result of the expression since it's a foregone conclusion that the overall or expression has a true value. If a is false then we evaluate b and return its value regardless of what b is since whatever its truthiness is represents what the or expression's truthiness is. This short-circuiting opens up some niceties like providing a default value, e.g. a or "default". To help visualize this, if we ignore a or b as an expression and treat it as result = a or b, then we can unravel this all to:

result = a
if not a:
    result = b
Unravelling a or b

For a and b, if a is false then it's immediately returned since there's no need to know the value of b as it's a foregone conclusion; the and expression is going to be false. If a is true, though, then the value of b is the result of the expression. For result = a and b, we can unravel that to:

result = a
if a:
    result = b
Unravelling a and b

The unravelled syntax

We can actually simplify/shrink our statement examples thanks to assignment expressions. For instance, result = a or b can become:

if not (result := a):
    result = b
Unravelling a or b using assignment expressions

The equivalent for a and b is:

if (result := a):
    result = b
Unravelling a and b using assignment expressions

The problem with these examples is they still turn an expression into a statement. For instance, if you had spam(a or b) then you couldn't use the example I used above.

But if we add in conditional expressions, we can actually convert this approach to an expression! For a or b, we can make it be temp if (temp := a) else b; for a and b, it can be temp if not (temp := a) else b (historical footnotes about conditional expressions: Guido polled the audience at PyCon US 2005 over the various syntax proposals for conditional expressions, and if you read his pronouncement email you will notice people hating on him for syntax decisions like assignment expressions that ultimately caused Guido to retire as BDFL had been going on for decades).

Now some astute readers might raise an objection over my claim that temp if (temp := a) else b is equivalent to a or b because there is now a temp variable in existence. This is true, but I don't think it's much of a concern because CPython itself cheats like this somewhat. 😄

CPython's cheating at variables

Have you ever looked at the local variables that exist when you use a list comprehension?

>>> [locals() for _ in range(1)]
[{'.0': <range_iterator object at 0x109261480>, '_': 0}]
The locals within a list comprehension

Notice the .0 variable? That's a temp variable that Python creates to hold the iterator of the for loop. This is an implementation detail that most people never care about since the name isn't valid Python syntax. But for my purposes it shows that using a temp variable that the user isn't aware of is not unheard of. It also shows that the compiler can use names that users will never accidentally clash with, and some transpiler could always make sure to choose names that the user doesn't use in any way. In other words, I don't think I'm cheating here in a way that matters. 😉