Afaan Ashiq

Software Engineering

Assert Statements in Production

September 10, 2022 3 min read

In this post I talk about the assert keyword and why I think it should not be used in production code.


Table of Contents


The assert keyword

The assert keyword is used in Python to check the equality/truthiness of elements. Let’s take the following snippet:

x = 1
assert x == 1

This assert statement will be considered to be passed and will not throw an error.

Taking the following snippet:

>>> x = 1
>>> assert x == 2
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
AssertionError

When the assert statement does not hold True then an AssertionError will be thrown.


A common assert usage

Using this mechanism, many developers use the assert statements as a layer of input validation. Let’s look at the following function:

def get_food_for_dog_breed(dog_breed: str) -> str:
    supported_dog_breeds = ("Jackapoo", "Labrador Retriever")
    assert dog_breed in supported_dog_breeds
    ...

As you might have guessed the purpose of this function is to fetch the type of dog food which corresponds to a given dog_breed.

What is important here is the 2nd line of the body of the function.
We are using the assert statement to validate our input.
Presumably because we do not support any other input outside the 2 we have shown.
This makes sense and is a reasonable thing to do.

>>> get_food_for_dog_breed(dog_breed="Pomeranian")
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
  File "<input>", line 2, in get_food_for_dog_breed
AssertionError

When we try and pass an input that is not supported by the get_food_for_dog_breed function, an AssertionError will be thrown.
One issue with this is that the caller of our function will be met by an AssertionError for their efforts.
But arguably, we have not been clear enough as to what went wrong.
The caller will not be made aware immediately that their input was of an unsupported dog breed!

High quality code always clearly conveys our intentions to the reader or the user.

And in this case, we can be much clearer to our user as to what went wrong.


The PYTHONOPTIMIZE flag

However, the other and perhaps even more significant issue here is that the assert keyword can be switched off globally across your Python intepreter.
This is often done at the infrastructure level to provide additional optimizations via use of the PYTHONOPTIMIZE flag.

So unwittingly, we can easily disable this key piece of functionality without being aware of the impact. In the case of data validation, I would always recommend raising an exception instead of using the assert keyword. In our case, I would be inclined to throw a custom exception.
In doing so, we can be clear to the caller about what has happened.
More importantly we are telling them why their input has been invalidated.

class UnsupportedDogBreedError(Exception):
    ...

def get_food_for_dog_breed(dog_breed: str) -> str:
    supported_dog_breeds = ("Jackapoo", "Labrador Retriever")
    
    if dog_breed not in supported_dog_breeds:
        raise UnsupportedDogBreedError()
    ...

By throwing the UnsupportedDogBreedError custom exception we have clearly stated what happened in this failure scenario. We can leave it to the caller to handle this case in whichever way they see fit.