Python Assert Statement

Python Assert Statement: Learn It in 7 Steps

The Python assert statement is one of the tools that as a Python developer is available to you to make your programs more robust.

What is the Python assert statement?

The assert statement allows to verify that the state of a Python program is the one expected by a developer. Expressions verified by assert should always be true unless there is an unexpected bug in a program.

In this article we will learn how to use the assert statement in your Python applications.

Let’s get started!

1. Practice a Very Basic Example of Assert Statement

To show you how assert works we will start with a basic expression that uses assert (also called assertion).

Open a terminal and type Python to open a Python Interactive Shell:

$ python
Python 3.7.4 (default, Aug 13 2019, 15:17:50) 
[Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

Execute the following expression that is logically true:

>>> assert 5>0

As you can see nothing happens…

Now execute a different expression that is logically false:

>>> assert 5<0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

This time we see an exception raised by the Python interpreter and the exception is of type AssertionError.

From this example we can define the behaviour of the assert statement.

The assert statement verifies a logical condition. If the condition is true the execution of the program continues. If the condition is false assert raises an AssertionError.

In this case the error that we are returning is not very clear…

…what if we want the AssertError to also provide a message that explains the type of error?

To do that we can pass an optional message to the assert statement.

assert <condition>, <optional message>

Try to update the previous expression with the following message:

>>> assert 5<0, "The number 5 is not negative"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: The number 5 is not negative

This time we get back an exception that clearly explains what’s causing the error.

2. Compare Assert vs Raise in Python

To give you a full understanding of how the assert statement behaves we will look at alternative code using raise that behaves like assert does.

if __debug__
    if not <condition>:
        raise AssertionError(<message>)

Let’s apply it to our example:

if __debug__:
    if not 5<0:
        raise AssertionError("The number 5 is not negative")

As you can see below, the behaviour is identical to the one of the assertion we have seen in the previous section:

>>> if __debug__:
...     if not 5<0:
...         raise AssertionError("The number 5 is not negative")
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
AssertionError: The number 5 is not negative

The second and third line of our code are pretty self-explanatory.

But what about the first line? What is __debug__?

Let’s see if the Python shell can answer this question:

>>> __debug__
True

Interesting, so __debug__ is True. This explains why the second and third lines of our code are executed.

But this still doesn’t tell us what __debug__ is…

It’s not something we have defined. This means it’s something that is provided out-of-the-box by Python as a language.

According to the Python documentation __debug__ is a built-in constant that is true if you don’t execute Python with the -O flag.

Let’s find out if this is really the case…

Open a new Python interactive shell using the -O option:

$ python -O
Python 3.7.4 (default, Aug 13 2019, 15:17:50) 
[Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> __debug__
False

This time the __debug__ constant is False. And if we run the previous code…

>>> if __debug__:
...     if not 5<0:
...         raise AssertionError("The number 5 is not negative")
... 
>>> 

Nothing happens. Obviously because the first if condition is false.

3. How to Disable Assertions in Python

In the previous section I have given you a hint on how you can disable assertions in Python.

Let’s use a different example to explain this…

Create a new Python program called assert_example.py that contains the following code:

month = "January"
assert type(month) == str
print("The month is {}".format(month))

We use an assert to make sure the variable month is of type String and then we print a message.

What happens if the value of the variable month is not a String?

month = 2021

We get the following output:

$ python assert_example.py 
Traceback (most recent call last):
  File "assert_example.py", line 2, in <module>
    assert type(month) == str
AssertionError

As expected we get an AssertionError back.

And now…

…we add the -O flag when we run the program:

$ python -O assert_example.py 
The month is 2021

Interesting and scary at the same time!

The assert statement has not been called or to be more precise has been disabled.

The Python -O flag disables the execution of assert statements in your Python program. This is usually done for performance reasons when generating a release build to be deployed to Production systems.

In this case you can see that the missing assert logic has introduced a bug in our program that just assumes that the month is in the correct format.

In the next section we will see why this is not necessarily the correct way of using assert.

4. Do Not Validate Input Data Using Assert

In the previous example we have seen how disabling assertions made our program behave incorrectly.

This is exactly what shouldn’t happen if you disable assertions. The assert statement is designed to be used to test conditions that should never occur, not to change the logic of your program.

The behaviour of your program should not depend on assertions and you should be able to remove them without changing the way your program works.

Here’s a very important rule to follow…

Do not use the assert statement to validate user input.

Let’s see why…

Create a program that reads a number using the input function:

number = int(input("Insert a number: "))
assert number>0, "The number must be greater than zero"
output = 100/number
print(output)

We use the assert statement to make sure the number is positive and then we calculate the output as 100 divided by our number.

$ python assert_example.py 
Insert a number: 4
25.0

Now, let’s try to pass zero as input:

$ python assert_example.py 
Insert a number: 0
Traceback (most recent call last):
  File "assert_example.py", line 2, in <module>
    assert number>0, "The number must be greater than zero"
AssertionError: The number must be greater than zero

As expected the assert statement raises an AssertionError exception because its condition is false.

Now let’s execute Python with the -O flag to disable assertions:

$ python -O assert_example.py 
Insert a number: 0
Traceback (most recent call last):
  File "assert_example.py", line 3, in <module>
    output = 100/number
ZeroDivisionError: division by zero

This time the assertions is not executed and our program tries to divide 100 by zero resulting in the ZeroDivisionError exception.

You can see why you should not validate user inputs using assertions. Because assertions can be disabled and at that point any validation using assertions would be bypassed.

Very dangerous, it could cause any sort of security issues in your program.

Imagine what would happen if you used the assert statement to verify if a user has the rights to update data in your application. And then those assertions are disabled in Production.

5. Verify Conditions That Should Never Occur

In this section we will see how assert statements can help us find the cause of bugs faster.

We will use assert to make sure a specific condition doesn’t occur. If it does then there is a bug somewhere in our code.

Let’s have a look at one example with a Python function that calculates the area of a rectangle:

def calculate_area(length, width):
    area = length*width
    return area

length = int(input("Insert the length: "))
width = int(input("Insert the width: "))
print("The area of the rectangle is {}".format(calculate_area(length, width)))

When I run this program I get the following output:

$ python assert_example.py 
Insert the length: 4
Insert the width: 5
The area of the rectangle is 20

Now, let’s see what happen if I pass a negative length:

$ python assert_example.py 
Insert the length: -4
Insert the width: 5
The area of the rectangle is -20

This doesn’t really make sense…

…the area cannot be negative!

So, what can we do about it? This is a condition that should never occur.

Let’s see how assert can help:

def calculate_area(length, width):
    area = length*width
    assert area>0, "The area of a rectangle cannot be negative"
    return area

I have added an assert statement that verifies that the area is positive.

$ python assert_example.py 
Insert the length: -4
Insert the width: 5
Traceback (most recent call last):
  File "assert_example.py", line 8, in <module>
    print("The area of the rectangle is {}".format(calculate_area(length, width)))
  File "assert_example.py", line 3, in calculate_area
    assert area>0, "The area of a rectangle cannot be negative"
AssertionError: The area of a rectangle cannot be negative

As expected we get an AssertionError exception because the value of area is negative.

This stops the execution of our program preventing this value from being potentially used in other operations.

6. Parentheses and Assert in Python

If you are using Python 3 you might be wondering why in the previous assert examples we have never used parentheses after assert.

For example, as you know in Python 3 you write a print statement in the following way:

print("Message you want to print")

So, why the same doesn’t apply to assertions?

Let’s see what happens if we take the previous assert expression and we surround it with parentheses:

>>> number = 0
>>> assert(number>0, "The number must be greater than zero")

Try to run this assert statement in the Python shell. You will get back the following error:

<stdin>:1: SyntaxWarning: assertion is always true, perhaps remove parentheses?

But, why the error says that the assertion is always true?

That’s because the assert condition, after adding the parentheses, has become a tuple.

The format of a tuple is (value1, value2, …, valueN) and a tuple in a boolean context is always True unless it doesn’t contain any values.

Here is what happens if we pass an empty tuple so an assert statement:

>>> assert()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

Python raises an AssertionError exception because an empty tuple is always false.

Here is how you can verify the way a tuple gets evaluated as a boolean:

>>> number = 0
>>> print(bool((number>0, "The number must be greater than zero")))
True

>>> print(bool(()))
False

An empty tuple translates into False in a boolean context. A non empty tuple translates to True.

Below you can see the correct way of using parentheses with assert:

>>> number = 0
>>> assert(number>0), "The number must be greater than zero"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: The number must be greater than zero

So, remember to be careful when using parentheses with assert to avoid bugs caused by the fact that you might be thinking your assert is correct when in fact it doesn’t perform the check you expect.

7. The Assert Statement in Unit Tests

Assert statements are also used in unit tests to verify that the result returned by a specific function is the one we expect.

I want to write unit tests for our function that calculates the area of a rectangle (without including the assert statement in the function).

This time we will pass the arguments via the command line instead of asking for length and width interactively:

import sys
  
def calculate_area(length, width):
    area = length*width
    return area

def main(length, width):
    print("The area of the rectangle is {}".format(calculate_area(length, width)))

if __name__ == '__main__':
    length = int(sys.argv[1])
    width = int(sys.argv[2])
    main(length, width)

The output is:

$ python assert_example.py 4 5
The area of the rectangle is 20

First of all, let’s create the test class…

Don’t worry about every single details of this class if you have never written unit tests in Python before. The main concept here is that we can use assert statements to perform automated tests.

Let’s write a simple success test case.

import unittest
from assert_example import calculate_area

class TestAssertExample(unittest.TestCase):

    def test_calculate_area_success(self):
        length = 4
        width = 5
        area = calculate_area(length, width)
        self.assertEqual(area, 20)

if __name__ == '__main__':
    unittest.main()

As you can see we import unittest and also the calculate_area function from assert_example.py.

Then we define a class called TestAssertExample that inherits another class, unittest.TestCase.

Finally, we create the method test_calculate_area_success that calculates the area and verifies that its value is what we expect using the assertEqual statement.

This is a slightly different type of assert compared to what we have seen until now. The are multiple types of assert methods you can use in your Python unit tests depending on what you need.

Let’s execute the unit test:

$ python test_assert_example.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

The test is successful.

What if I want to test a negative scenario?

I want to make sure an exception is raised by my function if at least one between length and width is negative. I can add the following test method to our test class and to test for exceptions we can use the assertRaises method:

def test_calculate_area_failure(self):
    length = -4
    width = 5
    self.assertRaises(ValueError, calculate_area, length, width)

Let’s find out if both tests are successful…

$ python test_assert_example.py 
F.
======================================================================
FAIL: test_calculate_area_failure (__main__.TestAssertExample)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_assert_example.py", line 15, in test_calculate_area_failure
    self.assertRaises(TypeError, calculate_area, length, width)
AssertionError: ValueError not raised by calculate_area

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

Hmmm, they are not!

There is one failure caused by the fact the the calculate_area method doesn’t raise a ValueError exception if either length or width are negative.

It’s time to enhance our function to handle this scenario:

def calculate_area(length, width):
    if length < 0 or width < 0:
        raise ValueError("Length and width cannot be negative")

    area = length*width
    return area

And now let’s run both unit tests again:

$ python test_assert_example.py 
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

All good this time 🙂

Below you can find the complete test suite:

import unittest
from assert_example import calculate_area

class TestAssertExample(unittest.TestCase):

    def test_calculate_area_success(self):
        length = 4
        width = 5
        area = calculate_area(length, width)
        self.assertEqual(area, 20)

    def test_calculate_area_failure(self):
        length = -4
        width = 5
        self.assertRaises(ValueError, calculate_area, length, width)

if __name__ == '__main__':
    unittest.main()

Conclusion

In this tutorial I have introduced the Python assert statement and showed you what is the difference between assert and a combination of if and raise statements.

We have seen that in Python it’s possible to disable assertions and this can be useful to improve the performance of Production builds.

This is also the reason why the assert statements should never be used to implement application logic like input validation or security checks. If disabled those assertions could introduce critical bugs in our program.

The main purpose of assert is to make sure certain conditions that go against the way our program should behave never occur (e.g. a variable that should always be positive somehow ends up being negative or a variable doesn’t fall within its expected range).

Finally we have learned why using parentheses in assert statements can lead to bugs and how the concept of assertion is extremely important when we want to develop unit tests to guarantee a robust code.

Which part of this article has been the most useful to you?

Let me know in the comment below 🙂

Share knowledge with your friends!

Leave a Reply

Your email address will not be published. Required fields are marked *