ADVANCED PYTHON PROGRAMMING

To Be, or Not to Be

This time, we talk about if statements—even though you probably get the gist, there's still a lot to say, from syntax to software design.

Dan Gittik
10 min readApr 6, 2020

--

After we covered assignment, we turn our attention to the next big thing: conditionals. The humble if statement actually has quite a few nuances—whether it’s about readability, performance, common pitfalls or rare insights, this deceivingly simple construct is at the heart of one of the most fundamental questions in software design.

Having the Best Words

Consider the statement “had I not known, I wouldn’t have come.”—it makes sense, but takes a moment to decode. Eventually, you figure out that it means I did come, because I knew something—in other words, “I knew, so I came”—but phrasing it in negative form and using past perfect for a tense make it unnecessarily complicated.

Sometimes it’s intentional: lawyers and other bureaucrats are notorious for using a form of English, often dubbed “Legalese”, that is so convoluted, nobody except them can make sense of their documents. Similarly, I often find texts in museum exhibitions to be very long, sophisticated paragraphs of very long, sophisticated words that, once deciphered, actually mean very little—and I suspect it’s also intentional: an attempt to sound smart and interesting, even when you don’t have anything smart or interesting to say.

Anyway, let’s stay down to earth and follow Eminem’s advice: if you ain’t got nothin’ nice to say then don’t say nothin’. We’ll cover four ways to make if statements nicer—before we arrive at a contradiction.

Reading Code

Let’s say we have a list of users, and we’d like to check whether it’s empty. What would you do?

Or…

The first example deals with a lower abstraction level than necessary, describing the implementation of the condition rather than it’s interface: “is the length of the list equal to zero” instead of “is the list empty”. The second example allocates an extra list, which is immediately discarded. How about…

It’s so readable it’s almost annoying—the code is as clear as the English. This might look minor, but in a codebase with thousands of lines of code, this cognitive load accumulates, same as with people: if you’d like to know the time, anyone with a watch is good enough; but if you’re studying quantum physics, the professor’s choice of words is important. Like Guido says: “code is read more than it is written”—readability counts.

Of course, the fact that it’s true doesn’t make it easy; and even this simple advice is can be overdone:

That’s just weird. It really should be:

Because… well, because it makes more sense. If this hiccup raises a red flag, or at least an eyebrow—good. We’ll get back to it in a moment; but first…

Don’t Worry, Be Happy

Another trick to make if statements nicer is to be optimistic—phrase conditions in their positive form, rather than the negative. Instead of:

Do:

Similarly, instead of…

Do:

Flat is Better Than Nested

Another good piece of advice is to avoid nesting—in code in general, and in if statements in particular. Consider this:

Instead, we could spread it out and address edge-cases separately, without forcing the readers to fit everything in their mental stack at once. Like so:

It’s longer, but it’s clearer. Instead of having to remember that if that, and that, and that is true, then this—we simply bail whenever things go south; so for every line of code we reach, we know that everything is right with the world, and can focus on the present instead of worrying about the past.

Compound Condition

Python has one more neat trick up its sleeve. Instead of doing this:

We ought to write:

Not only is it much more readable—it’s actually more performant in some cases, because every expression is evaluated exactly once. Here’s a more obvious example:

If items is a native list, checking its length is pretty fast—but if it’s a custom data structure, it might take longer. Writing it in this form forces Python to compute the length twice, even if it means counting every item in O(n) complexity.

This, on the other hand, computes the length exactly once, and Python reuses its value in both comparisons. It even reads more easily: “if the length isn’t between 2 and 1,000,000 then it’s invalid”.

The Contradiction

But wait—didn’t we say we should stay positive? What if flattening nested statements or compounding conditions forces us to choose, like in the last example? What’s more important?

The bad news is that you can’t know, really—not without experience in the rather philosophical (and as such, subjective) field of “software aesthetics”. The good news is that you’re not alone: this contradictory advice is common to all fields of design—and, more generally, philosophy.

Just consider the English sentence: “Brad Pitt, in the movie Snatch, plays a gipsy who boxes for a living”. If you read The Elements of Style—a seminal book on good writing style—you’d notice several faults in the sentence.

One fault is that it’s unnecessarily verbose—someone who boxes for a living can be described in one word instead of five: a boxer. It would be shorter and clearer to say “Brad Pitt, in the movie Snatch, plays a gypsy boxer”; and in relation to code, consider this:

That, too, is unnecessarily verbose—it would be shorter and clearer to say:

Another fault with the sentence is that it requires the reader to open a set of mental parenthesis, and for no good reason. “Hey, let’s talk about Brad Pitt!” it says. “By the way, we’re talking about the movie Snatch. Anyway, Brad Pitt! He’s playing a gypsy boxer!” All you have to do to avoid it is transpose the clauses: “In the movie Snatch, Brad Pitt plays a gypsy boxer”. Similarly, I sometimes see code like this:

Again, we have to divert some of our attention from the important part to the fact that we’re in the middle of some grammatical construct, whether in English or in Python. Instead, we could do this:

And make it easier for the reader to navigate the code. The problem with these examples is that they look easy, almost obvious—of course it’s better that way! But that’s because they’re educational examples, intended to illustrate a concept; real life is much messier, and in some cases, the rules don’t apply. It’s an important caveat: it means there’s no silver bullet, and it raises the question of whether we should even bother with learning the rules. William Strunk, the author of The Elements of Style, puts it nicely:

It is an old observation that the best writers sometimes disregard the rules of rhetoric. When they do so, however, the reader will usually find in the sentence some compensating merit, attained at the cost of the violation. Unless he is certain of doing as well, he will probably do best to follow the rules.

Comic Relief

That was heavy; let’s finish with some fun stuff, instead. We’re still on the topic of conditionals, and it’s not complete without mentioning short circuitry and the ternary operator.

Short Circuits

Short circuitry has to do with how and and or expressions are evaluated. You might expect 1 and 2 to be True, but it’s actually not: it’s 2—like, the actual number “two”—which just so happens to evaluate to True, so if you use it in a condition, you wouldn’t even notice. Here are a few more examples:

Python is actually quite lazy in evaluating and and or expressions. With and expressions, it stops as soon as some clause is False—because this means the entire expression is going to be false, so why bother evaluating the rest of it. Spitefully enough, Python returns the guilty party: so [] and 10 evaluates to [], whose fault it is the expression turned false. When Python gets to the last clause, it just returns it as-as, like 2 in 1 and 2, because if it got this far, it means this clause will effectively determine the boolean value of the entire expression.

With or expressions it’s the other way around: Python stops as soon as some clause is True, because this means the entire expression is going to be true; and again, it returns the guilty party, like with 5 or 8. If, on the other hand, all the clauses are false, it eventually reaches the last one and returns it as-is, like with 0 or {}—for the destiny of the entire expression is in its hands.

This allows for some nifty tricks. or, for example, can be used to replace empty values with default values in a shorter, more readable manner. Instead of:

We could write:

You could argue that it’d be enough to just add it as a default argument:

But this wouldn’t work for invocation such as hello('') or hello(None), and, more generally—only works for static default arguments; if we’d have a dynamic one, like a datetime argument defaulting to the current time, we’d have to resolve it when the function runs, and we’d have to choose between the first two options:

With and, it’s the other way around: you can make sure some value is not empty before using it. In fact, short circuitry was used for that in languages as old as C, where you’d have a pointer that might be null, so you’d have to check it before dereferencing it. Something like if (p != NULL && *p == 1) would not be possible, unless the evaluation stops as soon as the expression turns false; the null check effectively guarantees that if it fails, the following clauses won’t be evaluated—so if they are, they can safely dereference the pointer.

What does it have to do with Python, you ask? Imagine a function that receives an object for an argument, which may or may not be None: for example, a long-running computation that receives a log object to report back on its progress, and just runs silently if it’s omitted. Something like this:

That’s a bit… tedious. If only there was a way to compress these boilerplate log messages.

It seems a bit weird at first—but that’s only because we turned log statements to log expressions, whose value is always discarded. However, it being an expression, it needs to be evaluated: first, log, which can be None—and only if it’s not, log.debug(...), which emits the log message and returns… whatever. So, null pointer checks in Python!

The Ternary Operator

You might be familiar with the ternary operator from other languages, like C and Java. It goes like this: condition ? then-value : else-value. Nothing fancy, just a concise way to express a conditional value without spreading it out over 4 lines. Python didn’t have this feature in its early versions, so people resolved to some clever and/or trickery instead:

That’s looks freaky, but only because of the confusing precedence rules. With parenthesis, it’s much clearer:

If the condition is true, the overall value of the and expression would be the then_value, and that’d be enough for the or expression, too. If, on the other hand, it’s false, the overall value of the and expression is false, and the or expression’s only hope is with the else_value, which is returned. This has the unfortunate edge-case where then_values that just so happen to evaluate to false didn’t work, because then the or expression would defer to the else_value:

So, Python 2.4 introduced Python’s ternary operator:

Its structure takes getting used to, but hey—it works!

And it doesn’t make your brain melt like the and/or trick, so you should definitely use it instead.

Conclusion

And that concludes our discussion on conditional statements. It’s fun how even a subject as basic as if statements has enormous depth when it comes to software design. Even more fun is to have a chapter on if statements in a syllabus for advanced Python, and see how the experienced developers in the audience go from rolling their eyes to listening intently. That’s why “to be, or not to be”, as basic as it sounds (and is), will always be on people’s minds as this famous quote, this unresolved question.

The Advanced Python Programming series includes the following articles:

  1. A Value by Any Other Name
  2. To Be, or Not to Be
  3. Loopin’ Around
  4. Functions at Last
  5. To Functions, and Beyond!
  6. Function Internals 1
  7. Function Internals 2
  8. Next Generation
  9. Objects — Objects Everywhere
  10. Objects Incarnate
  11. Meddling with Primal Forces
  12. Descriptors Aplenty
  13. Death and Taxes
  14. Metaphysics
  15. The Ones that Got Away
  16. International Trade

--

--

Dan Gittik

Lecturer at Tel Aviv university. Having worked in Military Intelligence, Google and Magic Leap, I’m passionate about the intersection of theory and practice.