Python is a dynamically typed programming language. This means it assigns types to variables whilst the program is running, as opposed to getting its types enforced via the programmer and/or a compiler. Whilst Python has added type hints to allow programmers to mark up their code with types, these act more like suggestions than rules when compared to alternative languages with strict type systems such as Rust. Thankfully, python does have a strong type system - forbidding operations between nonsensical types instead of silently validating or failing on them.
Let's explore some caveats with determining negatives in Python. We'll use a series of items we would typically associate with a negative state:
The
False
value0
An empty list
[]
An empty string
''
The
None
valueAn empty dictionary
{}
An empty tuple
()
bool
typecast and the not
negation
If we cast these to the bool
type, we are able to confirm that Python can determine that all of these are False
.
If we check these same test conditions with the not
keyword, we get a similar result:
This is great. Python has a convenient way for us to simply determine if a normal data structure is Truthy
or Falsy
and all our problems are solved.
Explicit False
check
You might be wondering if we could have been more explicit. Why didn't we just check if the value was False
directly?
Here we have checked the items in two ways. The first, with ==
, is a value-based equality to False
, which provides two positive matches:
False == False
0 == False
What this check is doing is more that just checking if these values are "falsy" - we need to know if they are False
. That's right - "falsy" and False
do not mean the same thing; False
is stricter. In python 0
is considered equal to False
in addition to also being "falsy".
The second check, with is
, is a reference-based equality to False
, which provides one positive match - False is False
. The is
operator checks if both sides point to the same object in memory, which is why we only match when we compare the same object to itself.
None
for me thanks
What about if we just check if nothing was there at all:
It turns out that for both value-based equality and reference-based equality, only the None
'falsy' item was picked up. This is because most 'falsy' things still represent an actual "thing" - the thing just happens to be negative. None
represents the absence of a thing entirely, which is why you can have Optional[bool]
types to indicate that sometimes a boolean is provided or it's just an undefined nothingness.
The trickiness with None
s is that they often aren't just written in code but appear implicitly. A classic example will be from returning a function like so:
As you can see here, in both cases this empty_function()
is caught by the if
condition. This is because it returns nothing... which is None
- it's just not written in the program explicitly.
In summary, the best solution for broad-spectrum negation is simply not
. If you require more strict false checking, you will need to determine your use case as appropriate. You should also be mindful that None
is sometimes implicit, and the differences between value-based equality with ==
and reference-based with is
.