Syntactic Sugar for 'is not None'

The following construct occurs very frequently:

if item is not None:
    process_item(item)
else:
    error_handling()

With longer functions, this causes a readability conundrum:

Option 1: Leave it as is but introduce a double negation (if not ... else), which is generally something that should be avoided if possible.

Option 2: Turn it around so that the error processing comes first:

if item is None:
    error_handling()
else:
    process_item(item)

This would require the reader to first parse the exception case before really knowing what the positive outcome was so they would have to jump to the bottom part first.

My idea would be to introduce syntactic sugar for the not None construct, e.g. a Some or Any keyword (or anything else that is clear and succinct).

This would be semantically the same as option 1 and would get rid of the double negative:

if item is Any:
    process_item(item)
else:
    error_handling()

There is a downside of course that it requires implicit knowledge that the Any keyword is actually related to the None keyword and therefore might introduce cognitive load in its own right.

Any thoughts about this?

Without hacks – pretty impossible. is operator just checks whether id(a) == id(b) (I’m simplifying a little) but Any cannot have the same id as all other objects. And there is no supported way of overloading is if my memory doesn’t fail me.

Also, remember that when we analyze typehints (for example, for metaprogramming or introspection or tooling), we might need to check whether a type hint is Any. What would we do then?

There are so many variations on this theme that we would be silly to insist on One True Way of writing it. For example, in my own code, the most common variation is

if item is None:
    item = default()
process_item(item)

While there are many good ways to handle this, writing a double negative:

if not (item is not None):

is never one of them. That would be unspeakable. There’s no conundrum here, it’s a paddlin’.

Coming back to your suggestion, I am afraid that you seem to be working under a misapprehension about None. While it is true that None is now a keyword, it is also an object, like every other value in Python.

is None and is not None are not special syntax forms, but plain old operators with an operand which happens to be the None object.

And so it would be for your proposed is Any, which would have to check whether the expression on the left evaluates to the actual Any object.

Now, of course we could hypothetically make a special syntactic form is Any which compiles to is not None, but that sort of special case in the language is even more unspeakable than double negatives.

There is no “might introduce cognitive load” here. It would definitely introduce cognitive load.

It would complicate Python’s execution model. Instead of is being a simple operator with which always checks whether the two operands are the same object, it would sometimes stand for the reverse test (“are they not the same object?”) with the second operand being unstated.

Worse, it would break backwards compatibility. “Any” is currently an ordinary name free for people to use. There’s a very important object in the stdlib by that name:

from typing import Any

With this proposal, you would make it impossible to test whether an object is the Any object using is, because is Any would have the special magical meaning.

Okay, perhaps not impossible. Maybe you could write:

Any is myobject

instead, but that leads to the worse result that now is is no longer symmetric:

a is b
b is a

would no longer have the same meanings. (There are many non-symmetric operations in Python, and maths, such as subtraction, division, but for is to be non-symmetric would be just weird.)

Fundamentally, this would be a confusing and strange “Wot?” operation that messes up the relatively clean Python execution model, for the sake of … what? Saving a few characters typing?

item is not None
item is Any
# doesn't actually test whether item is Any, but tests whether item is not None

One last thing… is not None should not be read as involving not None. The operator is is not, and the operand is still None. not None would result in True.

4 Likes

@steven.daprano You are raising some very good points I hadn’t considered. Thank you.
I agree with basically all of them.

Just to clarify: the conundrum was not that someone would write an atrocity like if not (item is not None): but that the if item is not None ... else already contains a double negative in the else branch.

So I wonder whether there is a way to have it both ways: Prevent the double negative and have the positive branch on top.

I see your solution to this problem and I think that’s a very nice way of doing it if you are fine with losing the None in the process. But if you want explicit message handling on the None, I think it would not work.

If you can think of a solution off the top of your head that solves the conundrum, it would be much appreciated.

Of course I could solve it like this, but introducing an extra variable just for this does not seem very clean:

item_ok = item is not None
if item_ok:
    process_item(item)
else:
    error_handling()

Not to me, it doesn’t. I read the else branch as “else if item is None” (which is definitely not a double negative, to my reading). Maybe that reading isn’t as obvious to non-native English speakers?

Ignoring the is/is not issue for a moment, I generally prefer to position the shorter clause first, so it isn’t hidden after the longer clause (sometimes off-screen). That does often mean it’s the error-handling clause (or something else very short, like continue, break, etc). Easy visibility of minimalist clauses isn’t the only consideration though, so it can’t be a hard and fast rule. Still, as Uncle Timmy taught us, “readability counts.”

BTW, I don’t think any special syntax for is not None would be all that helpful. I type fast enough and it reads pretty much like plain English.

@smontanaro Yeah, I prefer that as well. I like getting the short special cases out of the way when I read the meat and potato of a section so that I don’t have to keep the error handling in the back of my head while trying to make sense of a piece of code. Especially if the section is long and you don’t see the else when it is off screen (although we should avoid long sections like that generally).

On the other hand, I had an argument with a colleague who prefers the main part on top, and he said he wanted to dive right into what the section does. I can agree to his point of view, because it makes sense to understand what a section does before understanding how it should handle edge cases.

Still prefer the version you described in most cases.