Programming for fun and profit

A blog about software engineering, programming languages and technical tinkering

Sun 06 November 2022

The pre Python 2.5 ternary operator hack

Posted by Simon Larsén in Programming   

The modern day ternary operator is well-known to most Pythonistas:

<expr_if_true> if <condition> else <expr_if_false>

It's officially known as a conditional expression and was introduced back in Python 2.5 with PEP 308. Some like it, some don't, and while discussing it with a colleague of mine he mentioned that there "used to be something a whole lot worse" around the code bases written by developers favoring ternary operators. He couldn't remember what it looked like as these events are 15 years in the past, but a few days later he came back to me with a code snippet like this:

["nope", "yep"][False]

What?

No ternary operator you say?

Developers are creative and opinionated. Sometimes this mix leads to monstrosities, such as when creative developers who really liked ternaries created this pattern:

[<expr_if_false>, <expr_if_true>][<condition>]

You're reading that right. It's a list with two elements, the first of which is returned if the condition is False and the second if it's True. Here is an example:

condition = True

message_modern = "success" if condition else "failure"
print(message_modern)   # success

message_old = ["failure", "success"][condition]
print(message_old)      # success

This will make perfect sense to a C programmer: True and 1 are interchangeable, as are False and 0. And that's precisely how this works, the underlying function that implements the list index access performs some rudimentary checks and then just uses the provided index as an offset to the base pointer of the list.

But the hack isn't quite the same

At first glance, it may seem like the old hack with the list is functionally equivalent to the modern ternary operator. It isn't quite, though, because it lacks one very important property: lazy evaluation of the branches. In short, the ternary operator only evaluates the branch that it returns. Here's an example:

condition = False

result_modern = 1 / 0 if condition else 42
print(result_modern) # 42

result_old = [42, 1 / 0][condition] # raises ZeroDivisionError: division by zero
print(result_old)

The ternary operator does not result in a crash on division by zero as it does not evaluate the expression in the true-branch, whereas the old hack crashes immediately as it first evaluates both expressions and then returns one of them. To fully emulate the modern behavior we'd need something like this:

condition = False

result_old = [lambda: 42, lambda: 1 / 0][condition]()
print(result_old) # 42

Here we get lazy evaluation by virtue of wrapping the two branches in lambdas, and then executing the lambda that's returned. I think we can all agree that's not a pretty sight.

Conditional expressions are a good thing

Regardless of your stance on using ternary operators (or conditional expressions, as they're called in Python), it's probably a good thing they exist. Otherwise creative and opinionated programmers get around to hacks to emulate the behavior that end up being completely unreadable to others.