#08

Collapse if Able, else Expand

  • syntax
  • tricks
  • challenge

There are only 2 hard things in computer science: cache invalidation, and naming things.

Hey pips!

Remember list comprehensions? They let us condense a for loop onto 1 line:

>>> [word.capitalize() for word in ("collatz", "conjecture")]
["Collatz", "Conjecture"]

You can do the same with if blocks! Normally we’d write this:

>>> if "truthy":
        sup = 2.0
    else:
        sup = None

>>> sup
2.0

But we can collapse this into just:

>>> sup = (2.0 if "truthy" else None)
>>> sup
2.0

Damn, way more efficient! Let’s break down exactly what happened:

Constructing the ternary conditional

This is called a ternary conditional operatorconditional since it behaves depending on a condition, and ternary because it involves 3 operands. Generalising, it looks like this:

Breaking down the ternary conditional

So if the condition is truthy, left is returned from the expression. Otherwise, right is returned.

It might seem a bit weird to have the condition in the middle, but it’s another one of Python’s quirks that becomes quite natural once you get used to it!

Now what if we had 2 conditions?

if is_happy():
    message = "pog"
elif is_sad():
    message = "sadge"
else:
    message = "..."

The elif doesn’t quite carry over. Instead, what we do is recurse the conditional:

message = "pog" if is_happy() else "sadge" if is_sad() else "..."

You can think of this like:

Recursing the ternary conditional

We end up with a whole chain of ‘this if that else this if that else just this’. That can get out of hand pretty quickly. To increase readability, we might consider splitting it over multiple lines:

message = (
    "pog" if is_happy() else
    "sadge" if is_sad() else
    "..."
)

But at this point, we haven’t really gained any efficiency or readability… we may as well have just used our original expression.

So this shorthand if expression can be really convenient, but like all things should be used only when suitable. It’s really nice when you have some very simple logic that picks between 2 values:

def absolute_value(x):
    return -x if x < 0 else x

Importantly, you should only use it when you want the value that’s returned by the expression, like when setting a variable. If you’re just calling different functions:

if is_happy():
    rofl()
elif is_sad():
    weep()
else:
    idle()

Then here condensing isn’t great:

rofl() if is_happy() else weep() if is_sad() else idle()

Readability aside, it’s non-obvious that we’re just calling functions in-place, rather than trying to get their output.

If we had the same function being called, but with different inputs, then a conditional would be pretty nice:

send_message(
    ":rofl:" if is_happy() else
    ":weep:" if is_sad() else
    ":idle"
)

Enjoy the efficiency, but use judiciously!


Further Reading

  • Woah, it is actually called the ternary conditional operator! – Wikipedia

Challenge

Can you write a list comprehension that doubles each number in a list if it is positive, but otherwise makes it zero?

>>> t = [2, -1, 18, 0, -100]

>>> (your_expression)
[4, 0, 36, 0, 0]


Question? Bug needs fixing? Or just want to nerd out over programming?
Drop a message in the GitHub discussion for this issue.