Assert Statements in Production
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.