Being negative with Python

Being negative with Python

Observations on the falsy behaviours of Python 3

·

3 min read

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 value

  • 0

  • An empty list []

  • An empty string ''

  • The None value

  • An 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.

Casting falsy items to

If we check these same test conditions with the not keyword, we get a similar result:

The 'falsy' values are

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?

Checking if the 'falsy' items are explicity

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 Nones 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.

Photo by Kim Gorga on Unsplash