In defense of Python assertions

code
Author

Richard Decal

Published

August 26, 2024

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.