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
assert
statement with an example? __debug__
constant in Python.- Controlling the behavior of
assert
statements. - Disabling assertions using the
-O
option andPYTHONOPTIMIZE
environment variable. - Debugging code using the
pytest
package.
π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ββ