assert in Python: checking an assumption while developing

assert checks an assumption during development and raises an AssertionError when it turns out false. It is never a way to validate user input.
5 min read
Believemy logo

Definition

While writing code, assumptions pile up unnoticed: this list is never empty here, this counter never drops below zero. None of it is written down in black and white, and that is what ends up costing time. The day one of those assumptions turns false, the program does not stop where it was broken: it blows up three functions later, on a message that no longer points at the culprit.

assert answers that annoyance. This statement checks that a condition holds at a precise point in the program. If it does, nothing happens. If it does not, Python raises an AssertionError and everything stops, at the exact spot where the assumption stopped being true.

Here is the most common form: an assumption placed at the top of a function.

PYTHON
def average(marks):
    # An average makes no sense on an empty list.
    assert len(marks) > 0, "the list of marks is empty"
    return sum(marks) / len(marks)

average([])
# AssertionError: the list of marks is empty

Two parts make up the full form: the condition to check, then an optional message placed after a comma. That message looks like decoration, and it is not: without it, the traceback shows the offending line and nothing more, leaving the reader to guess what was expected.


It is not an input check

The costliest misunderstanding is validating outside data with an assertion. The temptation makes sense: assert age >= 18 fits on one line, where the correct version takes three.

What forbids it is the fate of this keyword in optimised mode, when the interpreter is launched with the -O option. The matching lines are not skipped at runtime: they are stripped at compile time, as if nobody had ever written them.

Warning

A permission check written as an assertion lets through, in production, exactly what it was meant to block. No error, no trace in the logs: the code looks guarded, and it no longer is.

One question settles it: where does the checked data come from? The table puts each common situation next to the form that suits it.

SituationThe right form
A doubtful user inputAn if then a raise of ValueError
A network call that may failA try block around the call
An access right to enforceAn explicit exception, never an assertion
An assumption internal to the codeAn assertion, right where it belongs

So if the condition can turn false because of the outside world, it is not an assertion. Data coming from a form, from a file or from an API is part of that world, however much its source is trusted.


The parentheses trap

This keyword is not a function, so parentheses do not wrap around it. The habit built with print() still pushes people to add some, and Python accepts the line without complaint.

Writing assert (condition, "message") amounts to handing it a single argument: a two-element tuple. A non-empty tuple always counts as true. The assertion then succeeds every single time, whatever the real condition says.

PYTHON
# Silent: the tuple is always true,
# so the condition is never evaluated.
assert (total > 0, "negative total")

# Correct: no parentheses around the whole thing.
assert total > 0, "negative total"

Recent Python versions flag that form with a warning, provided warnings get read at all. It is the kind of mistake that leaves a whole battery of checks looking busy while none of them bites.


Where it belongs

Automated tests are its natural ground. A test function often boils down to three moves: prepare some data, call the code, place an assertion on the result. The most widespread test libraries in Python rest entirely on this keyword.

Good to know

pytest rewrites assertions as it loads test files. That is why a failure shows both compared values instead of a bare AssertionError.

Outside tests, the second use is the internal invariant, that assumption a developer makes without ever writing it down: this list is already sorted, this counter stays positive. An assertion turns it into a check for the price of one line, readable by whoever revisits the code six months later.

The third use is documentation that runs. An assertion at the top of a function announces what it expects, much like a docstring, with one difference: a docstring can drift away from the code without anyone noticing, while the assertion complains the moment something contradicts it.


Frequently asked questions

Question

Should an AssertionError be caught?

Almost never, and the reason lies in what it reports. A failing assertion does not describe an anticipated situation: it announces a defect in the code itself. Catching it amounts to hiding a bug instead of fixing it. If the case was foreseeable, a suitable exception should have been raised.

Question

How are assertions disabled in production?

By launching the interpreter with the -O option, which strips every assertion from the compiled code and sets the __debug__ variable to false. The speed gain stays tiny, but the mere existence of that option settles the rule: their disappearance must change nothing in how the program behaves.

Question

What is the difference with a conditional test followed by a raise?

Intent, above all. An if followed by a raise handles a case the program expects and knows how to deal with: an empty input, a missing file. An assertion declares an assumption that must never be false. The first stays alive in production, the second speaks to development and can vanish without the slightest consequence.

Related terms

Discover our python glossary

Browse the terms and definitions most commonly used in development with Python.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.