Python’s assert statements are one of several options for debugging code in Python.
Python’s assert is mainly used for debugging by allowing us to write sanity tests in our code. These tests are performed to ensure that a particular condition is True or False. If the condition is False, an AssertionError is raised, indicating that the test condition failed.
Understanding assert
Python’s assert keyword is used to write assert statements that contain a condition or assumption that is tested against the condition from the program that we expect to be true.
If the condition matches the expected condition, nothing is displayed on the console and the execution continues, otherwise, an AssertionError is displayed. This exception interrupts program execution and indicates that the condition test failed.
Syntax
The syntax of the assert statement is written in the following form:
assert [condition], [error message]
condition – the condition or assumption to be tested
error message – the error message we want to display in the console when the condition is failed.
The assert In Action
Let’s create some assert statements to perform code checks. Consider the following example, in which we are testing our program to see if it produces the expected results.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
def evaluate_num(num): if num > 5: return num * num else: return num * 2 val = evaluate_num(5) """Assert statement to check that upper code returns 10 on evaluating evaluate_num(5)""" assert val == 10, "Condition failed." # We'll get nothing |
The above code defines a function called evaluate_num that takes a parameter num. The function checks if the value of num is greater than 5. If it is, the function returns the square of num (num * num). Otherwise, if num is less than or equal to 5, the function returns num multiplied by 2 (num * 2).
The assert statement checks whether the variable val is equal to 10 after evaluating evaluate_num(5). In this case, evaluate_num(5) returns 10, which means that the assert statement is true and we’ll get nothing in the console.
Let’s see what happens when we pass a num greater than 5.
|
1 2 3 4 5 6 |
val = evaluate_num(6) """Assert statement to check that upper code returns 12 on evaluating evaluate_num(6)""" assert val == 12, "Condition failed." |
We called the evaluate_num with the argument 6. Since 6 is greater than 5, the function will square the number 6 (6 * 6) which makes the variable val equal to 36, which makes our assert statement false. As a result, we’ll get an AssertionError with the message "Condition failed.".
|
1 2 3 4 |
Traceback (most recent call last): .... assert evaluate == 12, "Condition failed." AssertionError: Condition failed. |
Controlling the Behavior of assert
We were able to write assertions in a single line by using the assert keyword, and this single-line assert statements are equivalent to the following expression:
|
1 2 3 |
if __debug__: if not evaluate == 12: raise AssertionError("Condition failed.") |
The above if __debug__ conditional would function similarly to the assert statement written in the preceding code.
As a result of the above expression, the syntax of the if __debug__ conditional would be:
|
1 2 3 4 5 6 7 8 9 10 |
if __debug__: if not condition: raise AssertionError(error message) # ----------------- OR ----------------- # # For simple form assert statement without error message if __debug__: if not condition: raise AssertionError |
__debug__
What exactly is __debug__, and how does it affect the behavior of assert statements in a Python program?
The __debug__ is a built-in constant in Python that is set to True by default. However, we can change this to False by running Python in optimized mode with the -O command line option or by modifying the PYTHONOPTIMIZE variable.
|
1 2 3 4 |
print(__debug__) ---------- True |
As we can see when we printed the __debug__, we got True which indicates that our Python is not running in optimized mode.
Let’s understand better with examples.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# test.py class Shopping: def __init__(self, product, price): self.product = product self.price = price def list(self): assert self.price > 0, "Price should not be 0 or negative." data = f"{self.product} is worth ${self.price}." return data item_1 = Shopping("Perfume", 250) print(item_1.list()) item_2 = Shopping("Band Aid", 0.75) print(item_2.list()) item_3 = Shopping("Denim", -35) print(item_3.list()) |
The above code defines the Shopping class, which has a __init__ method that takes two parameters, product and price. These parameters’ values are assigned to the instance variables self.product and self.price.
This class has another method called list that returns product information along with a price. This method includes an assert statement that determines whether the product’s price is greater than 0.
Then we created three instances of the Shopping class (item_1, item_2, and item_3) and passed in the various products and prices. When we run the above code, we get the following result.
|
1 2 3 4 5 6 |
Perfume is worth $250. Band Aid is worth $0.75. Traceback (most recent call last): .... assert self.price > 0, "Price should not be 0 or negative." AssertionError: Price should not be 0 or negative. |
The first two instances passed the test because the assert statement condition (self.price > 0) was met. As a result, we received the string, whereas in the third case, the price was set to -35, which did not satisfy the assert statement condition, and we received the AssertionError with the error message.
The following if __debug__ conditional is equivalent to the assert statement we created in the method list within the class Shopping. If we had used the following code instead of the assert statement in the above code, the code would have worked perfectly.
|
1 2 3 4 5 6 |
if __debug__: if not self.price > 0: raise AssertionError("Price should not be 0 or negative.") # Equivalent to assert self.price > 0, "Price should not be 0 or negative." |
Disabling Assertions
We can disable the assertion and prevent the AssertionError message from being displayed on the console. We’ll try it manually first, then look at other safe options.
We could disable the assertion manually if we set __debug__ to False. Let’s see if we can complete this task within our program.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# test.py class Shopping: def __init__(self, product, price): self.product = product self.price = price def list(self): if __debug__ == False: if not self.price > 0: raise AssertionError("Price should not be 0 or negative.") data = f"{self.product} is worth ${self.price}." return data item_1 = Shopping("Perfume", -1) print(item_1.list()) item_2 = Shopping("Band Aid", 0.75) print(item_2.list()) item_3 = Shopping("Denim", -35) print(item_3.list()) |
Within our method list, we added a code snippet that checks the value of __debug__, if __debug__ is set to False, it means that Python is running in optimized mode and the assertions are disabled.
Since assertions are disabled, the above code will produce no errors on the console.
|
1 2 3 |
Perfume is worth $-1. Band Aid is worth $0.75. Denim is worth $-35. |
Note: This is not a good practice and is not recommended method to disable assertions.
The -O Option
The -O flag is a command-line option that disables all assertions. Internally, this option sets the __debug__ constant to False.
|
1 2 3 |
D:\SACHIN\Pycharm\assert_in_python>python -O >>> print(__debug__) False |
Open the terminal and change the directory containing the Python file and run the following command:
|
1 2 3 4 |
D:\SACHIN\Pycharm\assert_in_python>python -O test.py Perfume is worth $-1. Band Aid is worth $0.75. Denim is worth $-35. |
The python -O test.py command enables the optimized mode and executes the Python file test.py. The -O flag instructs the Python interpreter to optimize the code by turning off assertions.
We would have gotten the AssertionError if we hadn’t used the -O flag.
|
1 2 3 4 5 |
D:\SACHIN\Pycharm\assert_in_python>python test.py Traceback (most recent call last): .... assert self.price > 0, "Price should not be 0 or negative." AssertionError: Price should not be 0 or negative. |
PYTHONOPTIMIZE Env Variable
By setting the PYTHONOPTIMIZE environment variable to 1, we can run Python in optimized mode.
To set PYTHONOPTIMIZE=1, enter the following command in the terminal. This command will automatically run Python in optimized mode.
|
1 2 3 4 5 6 |
D:\SACHIN\Pycharm\assert_in_python>set PYTHONOPTIMIZE=1 D:\SACHIN\Pycharm\assert_in_python>python test.py Perfume is worth $-1. Band Aid is worth $0.75. Denim is worth $-35. |
When we check the status of the __debug__ constant in the Python shell, it is automatically set to False.
|
1 2 3 |
D:\SACHIN\Pycharm\assert_in_python>python >>> print(__debug__) False |
To undo the optimized mode, use the command set PYTHONOPTIMIZE=0.
Performing Debugging
In this section, we’ll write a bunch of assert statements and then test them with pytest, a third-party package. This package contains a simpler syntax for writing tests.
Since this is an external package, we must install it by running the command pip install pytest in the terminal.
Make a Python file called test_file.py and place the following code, which includes tests, inside it.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# test_file.py import math from os.path import isdir # test_1 def test_sq(): assert 5 * 5 == 20 # test_2 def test_search(): assert "Py" in "GeekPython" # test_3 def test_dir(): assert isdir("test_file.py") # test_4 def test_type(): assert type([1, 2, 3]) == list class TestCondition: # test_5 def test_reverse(self): sequence = "GeekPython" assert sequence[:: -1] == "nohtyPkeeG" # test_6 def test_value(self): assert round(math.pi) == 3.14 |
Now, open a terminal, navigate to the directory containing the Python file test_file.py, and type pytest test_file.py.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
D:\SACHIN\Pycharm\assert_in_python\test>pytest test_file.py ==================== test session starts ==================== platform win32 -- Python 3.10.5, pytest-7.3.2, pluggy-1.0.0 rootdir: D:\SACHIN\Pycharm\assert_in_python\test plugins: anyio-3.6.2 collected 6 items test_file.py F.F..F [100%] ==================== FAILURES ==================== ____________________ test_sq ____________________ def test_sq(): > assert 5 * 5 == 20 E assert (5 * 5) == 20 test_file.py:6: AssertionError ____________________ test_dir ____________________ def test_dir(): > assert isdir("test_file.py") E AssertionError: assert False E + where False = isdir('test_file.py') test_file.py:12: AssertionError ____________________ TestCondition.test_value ____________________ self = <test_file.TestCondition object at 0x00000207D485FA60> def test_value(self): > assert round(math.pi) == 3.14 E assert 3 == 3.14 E + where 3 = round(3.141592653589793) E + where 3.141592653589793 = math.pi test_file.py:23: AssertionError ==================== short test summary info ==================== FAILED test_file.py::test_sq - assert (5 * 5) == 20 FAILED test_file.py::test_dir - AssertionError: assert False FAILED test_file.py::TestCondition::test_value - assert 3 == 3.14 ==================== 3 failed, 3 passed in 0.32s ==================== |
The output of our tests produced by pytest is shown above, and we can see that three of them failed and three passed. The output provided full details for the three failed tests.
Note: pytest collects tests based on a naming convention. By default, classes containing tests must begin with Test, and any function in a file that should be treated as a test must also begin with test_. pytest will run all files of the form test_*.py or *_test.py in the current directory and its subdirectories. More details on the naming convention.
Conclusion
assert is a built-in keyword in Python that is used to create assert statements that perform sanity checks in our code. It is used for testing and debugging.
The assert statement includes a condition that is used to determine whether the condition is True or False. If the condition is False, an AssertionError is thrown, indicating that the condition was not met.
Let’s recall what we’ve learned:
- What is an
assertstatement with an example? __debug__constant in Python.- Controlling the behavior of
assertstatements. - Disabling assertions using the
-Ooption andPYTHONOPTIMIZEenvironment variable. - Debugging code using the
pytestpackage.
πOther articles you might be interested in if you liked this one
β Python generators and the yield keyword – how they work?
β Performing high-level path operations using pathlib in Python.
β Understanding the different uses of asterisk(*) in Python.
β What is the difference between seek() and tell() in Python?
β Generate temporary files and directories using tempfile module in Python.
β How to change the string representation of the objects in Python?
β __init__ and __new__: The concept of initializer and constructor in Python.
β What are context managers in Python?
That’s all for now
Keep Codingββ
