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.
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 emptyTwo 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.
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.
| Situation | The right form |
|---|---|
| A doubtful user input | An if then a raise of ValueError |
| A network call that may fail | A try block around the call |
| An access right to enforce | An explicit exception, never an assertion |
| An assumption internal to the code | An 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.
# 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.
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
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.
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.
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.