The Story of None: Part 2 - Recognizing It
In part 1 of the Story of None we've seen this validation function:
def validate_end_date_later_than_start(start_date, end_date): if end_date <= start_date: raise ValidationError( "The end date should be later than the start date.")
We've decided that
end_date may be omitted (and
None), and that this function is therefore buggy: we'll get a
TypeError if it's called with
None as one of the arguments and
a date with the other.
A type check?
One reaction to a
TypeError is to do a type check to see whether
the values are really of the right types, in this case,
to only proceed if they are. Something like:
if type(start_date) == date: ...
if isinstance(start_date, date): ...
This works, but I think this signals the wrong thing to the reader of the code.
The reader may start wondering whether there are code paths that
start_date that is not a
date but something else;
we seem to be guarding against a set of other possibilities. But in
reality we're only guarding against one possibility:
It is a bit of cognitive load for the developer to consider
is what we're really checking for, and that
NoneType and that this isn't equal to the
date type. It's not
right to make a developer think about stuff they don't have to think
Let's instead be specific, and just handle
None, and avoid type
So we want to make sure that
present. That something is there. It's now very tempting to check
for their presence with a clause like this:
if start_date: ...
This again will work, at least in this particular case, where the arguments are dates.
But it won't work for other things, like a function where the arguments are numbers.
Why won't it work for numbers? Because
0 in Python evaluates to
False. And the number
0 doesn't mean that the number is
omitted. So if we were, for example, comparing
end_age, where the ages are integer numbers, we'd be in trouble if
we did something like this:
if end_age and start_age and end_age >= start_age: raise ValidationError("End age should come before start age")
end_age could be
0 would definitely come before a
start_age of say,
10, and we still don't raise
ValidationError. A bug!
We want to reduce the burden on the developer who has to reason about
this code, if we can, we should use a pattern that works for any
None can be a value. Doing so will make our code be
more regular and easier to understand.
So when we can, we should explicitly compare with
value == None?
== None either. It will work but it'll be a tiny bit slower and
it's not the Pythonic convention. Plus if the
__eq__ operator is overloaded
it may dive into that, which is what you don't really want.
Follow the convention so that other programmers will be able to read
your code more easily. In Python you compare for equality with
but for identity with
is. I won't go into the details of identity
versus equality here. Instead I'll say that you can always safely do
an identity comparison with
value is None
So how do you compare with
None in Python? You use:
value is None
and if you want to check whether something is not
None the idiom is:
value is not None
value === null and
Next time we'll apply this approach to our validation function.