Unit Testing and Static Analysis tools are an excellent means to help ensure the integrity of our software, but we don’t want to rely on them. They should be more like a second layer of verification. Ideally, we want our code to be self-validating. Our code should assert its own correctness. To help with this, most languages have a useful mechanism known as assertions.
In Python, assertions can be disabled by setting the PYTHONOPTIMIZE system variable to True, or by invoking the Python Interpreter with the -O or -OO options. When disabled, the code generator creates no code for the assertion statements, making them a useful tool to help to trace or profile code in debug mode. By default, assertions are enabled.
To make an assertion in Python, use the assert keyword, which takes the form of:
assert condition, data
The assert statement is a specialised form of the raise statement, if the condition fails, it raises an AssertionError passing it the data object. This will typically crash your application with a useful Traceback message. The optional data object is typically an error message, for example:
def f(x):
assert x < 0, "x must be negative"
return x ** 2
It essentially work like this:
def f(x):
if __debug__:
if not x < 0:
raise AssertionError("x must be negative")
I find assertions very useful in asserting facts. I often like to assert that the application or data is in a certain state, if not then there’s some misunderstanding or mistake upstream, in the requirements, or perhaps in some code I am evolving. I typically use asserts in private methods as public methods are constrained by contracts which should raise runtime exceptions if broken.
Here are a few examples:
assert user.is_admin(), "user is not an admin"
assert x != 0, "x cannot be zero"
sent = clientsock.send(bytes)
assert sent == len(bytes)
discounted_price = getDiscountPrice(product)
assert 0 <= discounted_price <= price, "miscalculated discount error"