Are Python assertions evil?
When new devs join my team, I inevitably hear that assertions are bad. This might be a spicy take, but I’m here to argue that assertions, when used correctly, can be great to use.
I use Python’s assert
statements a lot: for debugging, testing, and documenting assumptions. But before I make my case, let’s steelman the arguments against them.
The case against assertions
Assertions have two downsides, which are valid. Later, I will explain how my usage of assertions mitigates these downsides.
1. They can be sneakily disabled
When you run Python in optimized mode (python -O
), assertions are disabled. This is bad news if your code relies on them.
Now, in 10 years of using Python, I’ve never seen anyone use -O
. But, we shouldn’t assume that downstream users won’t.
2. AssertionErrors are not very informative
This is true, which is why we should only use it in specific circumstances.
How I use assertions
Design by Contract (DbC)
DbC is about defining specs for your code using:
- Preconditions: conditions that must be true before a function is called.
- Postconditions: conditions that must be true after a function is called.
- Invariants conditions that must be true throughout the execution of a function.
I use assertions to document these expectations. It’s like executable documentation. For example:
def fancy_processing(input_df: pd.DataFrame) -> pd.DataFrame:
# Precondition
assert list(input_df.columns) == sorted(input_df.columns), f"Columns should be sorted: {input_df.columns}"
# ...processing...
# Postcondidtion
assert output_df.columns == input_df.columns, f"Columns should not change: {output_df.columns} != {input_df.columns}"
return output_df
That’s just an illustrative example, but you don’t want to go overboard with obvious checks. Assert important properties are holding true, not obvious things. As I see it, these assertions should always pass if I implemented my application correctly.
For more robust DbC in Python, check out:
I am aware of the icontract, but I find that it makes code less readable than using @beartype
and assertions.
Debugging
When I am in the thick of coding, assertions are my sanity checks. Unsure of my tensor shapes? Assert it. Unexpected dtypes? Assert it.
Once the code’s working, I’ll prune the less useful asserts. Liberal asserts are useful to constrain the search space of bugs, but you don’t need all of them once the code is more production-ready.
When NOT to use assertions
Handling production errors
Assertions should never be used to handle errors which are expected to happen in production. For example, never try: ... except AssertionError: ...
. Instead, use meaningful exceptions.
Code that always needs to run
Never use assertions if you have code that must run. For example, don’t use assertions for sanitizing user input.
GPU Tensors
When dealing with GPU tensors, you should avoid anything that requires the CPU. Using things like print
, .item()
, and assert
implicitly require a syncronization with the CPU.
In conclusion
Assertions, when used wisely, can make your code self-documenting and easier to debug.