How do you test that a Python function throws an exception?
How does one write a test that fails only if a function doesn’t throw
an expected exception?
Short Answer:
Use the self.assertRaises method as a context manager:
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
Demonstration
The best practice approach is fairly easy to demonstrate in a Python shell.
The unittest library
In Python 2.7 or 3:
import unittest
In Python 2.6, you can install a backport of 2.7’s unittest library, called unittest2, and just alias that as unittest:
import unittest2 as unittest
Example tests
Now, paste into your Python shell the following test of Python’s type-safety:
class MyTestCase(unittest.TestCase):
def test_1_cannot_add_int_and_str(self):
with self.assertRaises(TypeError):
1 + '1'
def test_2_cannot_add_int_and_str(self):
import operator
self.assertRaises(TypeError, operator.add, 1, '1')
Test one uses assertRaises as a context manager, which ensures that the error is properly caught and cleaned up, while recorded.
We could also write it without the context manager, see test two. The first argument would be the error type you expect to raise, the second argument, the function you are testing, and the remaining args and keyword args will be passed to that function.
I think it’s far more simple, readable, and maintainable to just to use the context manager.
Running the tests
To run the tests:
unittest.main(exit=False)
In Python 2.6, you’ll probably need the following:
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
And your terminal should output the following:
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s
OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>
And we see that as we expect, attempting to add a 1 and a '1' result in a TypeError.
For more verbose output, try this:
unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Using Python’s assert to Debug and Test Your Code
Python’s assert statement allows you to write sanity checks in your code. These checks are known as assertions, and you can use them to test if certain assumptions remain true while you’re developing your code. If any of your assertions turn false, then you have a bug in your code.
Assertions are a convenient tool for documenting, debugging, and testing code during development. Once you’ve debugged and tested your code with the help of assertions, then you can turn them off to optimize the code for production. Assertions will help you make your code more efficient, robust, and reliable.
In this tutorial, you’ll learn:
- What assertions are and when to use them
- How Python’s
assertstatement works - How
assertcan help you document, debug, and test your code - How assertions can be disabled to improve performance in production
- What common pitfalls you might face when using
assertstatements
To get the most out of this tutorial, you should have previous knowledge of expressions and operators, functions, conditional statements, and exceptions. Having a basic understanding of documenting, debugging, and testing Python code is also a plus.
Getting to Know Assertions in Python
Python implements a feature called assertions that’s pretty useful during the development of your applications and projects. You’ll find this feature in several other languages too, such as C and Java, and it comes in handy for documenting, debugging, and testing your code.
If you’re looking for a tool to strengthen your debugging and testing process, then assertions are for you. In this section, you’ll learn the basics of assertions, including what they are, what they’re good for, and when you shouldn’t use them in your code.
What Are Assertions?
In Python, assertions are statements that you can use to set sanity checks during the development process. Assertions allow you to test the correctness of your code by checking if some specific conditions remain true, which can come in handy while you’re debugging code.
The assertion condition should always be true unless you have a bug in your program. If the condition turns out to be false, then the assertion raises an exception and terminates the execution of your program.
With assertions, you can set checks to make sure that invariants within your code stay invariant. By doing so, you can check assumptions like preconditions and postconditions. For example, you can test conditions along the lines of This argument is not None or This return value is a string. These kinds of checks can help you catch errors as soon as possible when you’re developing a program.
What Are Assertions Good For?
Assertions are mainly for debugging. They’ll help you ensure that you don’t introduce new bugs while adding features and fixing other bugs in your code. However, they can have other interesting use cases within your development process. These use cases include documenting and testing your code.
The primary role of assertions is to trigger the alarms when a bug appears in a program. In this context, assertions mean Make sure that this condition remains true. Otherwise, throw an error.
In practice, you can use assertions to check preconditions and postconditions in your programs at development time. For example, programmers often place assertions at the beginning of functions to check if the input is valid (preconditions). Programmers also place assertions before functions’ return values to check if the output is valid (postconditions).
Assertions make it clear that you want to check if a given condition is and remains true. In Python, they can also include an optional message to unambiguously describe the error or problem at hand. That’s why they’re also an efficient tool for documenting code. In this context, their main advantage is their ability to take concrete action instead of being passive, as comments and docstrings are.
Finally, assertions are also ideal for writing test cases in your code. You can write concise and to-the-point test cases because assertions provide a quick way to check if a given condition is met or not, which defines if the test passes or not.
You’ll learn more about these common use cases of assertions later in this tutorial. Now you’ll learn the basics of when you shouldn’t use assertions.
When Not to Use Assertions?
In general, you shouldn’t use assertions for data processing or data validation, because you can disable assertions in your production code, which ends up removing all your assertion-based processing and validation code. Using assertions for data processing and validation is a common pitfall, as you’ll learn in Understanding Common Pitfalls of assert later in this tutorial.
Additionally, assertions aren’t an error-handling tool. The ultimate purpose of assertions isn’t to handle errors in production but to notify you during development so that you can fix them. In this regard, you shouldn’t write code that catches assertion errors using a regular try … except statement.
Understanding Python’s assert Statements
Now you know what assertions are, what they’re good for, and when you shouldn’t use them in your code. It’s time to learn the basics of writing your own assertions. First, note that Python implements assertions as a statement with the assert keyword rather than as a function. This behavior can be a common source of confusion and issues, as you’ll learn later in this tutorial.
In this section, you’ll learn the basics of using the assert statement to introduce assertions in your code. You’ll study the syntax of the assert statement. Most importantly, you’ll understand how this statement works in Python. Finally, you’ll also learn the basics of the AssertionError exception.
The Syntax of the assert Statement
An assert statement consists of the assert keyword, the expression or condition to test, and an optional message. The condition is supposed to always be true. If the assertion condition is true, then nothing happens, and your program continues its normal execution. On the other hand, if the condition becomes false, then assert halts the program by raising an AssertionError.
In Python, assert is a simple statement with the following syntax:
assert expression[, assertion_message]
Here, expression can be any valid Python expression or object, which is then tested for truthiness. If expression is false, then the statement throws an AssertionError. The assertion_message parameter is optional but encouraged. It can hold a string describing the issue that the statement is supposed to catch.
Here’s how this statement works in practice:
>>>
>>> number = 42
>>> assert number > 0
>>> number = -42
>>> assert number > 0
Traceback (most recent call last):
...
AssertionError
With a truthy expression, the assertion succeeds, and nothing happens. In that case, your program continues its normal execution. In contrast, a falsy expression makes the assertion fail, raising an AssertionError and breaking the program’s execution.
To make your assert statements clear to other developers, you should add a descriptive assertion message:
>>>
>>> number = 42
>>> assert number > 0, f"number greater than 0 expected, got: {number}"
>>> number = -42
>>> assert number > 0, f"number greater than 0 expected, got: {number}"
Traceback (most recent call last):
...
AssertionError: number greater than 0 expected, got: -42
The message in this assertion clearly states which condition should be true and what is making that condition fail. Note that the assertion_message argument to assert is optional. However, it can help you better understand the condition under test and figure out the problem that you’re facing.
So, whenever you use assert, it’s a good idea to use a descriptive assertion message for the traceback of the AssertionError exception.
An important point regarding the assert syntax is that this statement doesn’t require a pair of parentheses to group the expression and the optional message. In Python, assert is a statement instead of a function. Using a pair of parentheses can lead to unexpected behaviors.
For example, an assertion like the following will raise a SyntaxWarning:
>>>
>>> number = 42
>>> assert(number > 0, f"number greater than 0 expected, got: {number}")
<stdin>:1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
This warning has to do with non-empty tuples always being truthy in Python. In this example, the parentheses turn the assertion expression and message into a two-item tuple, which always evaluates to true.
Fortunately, recent versions of Python throw a SyntaxWarning to alert you of this misleading syntax. However, in older versions of the language, an assert statement like the one above will always succeed.
This issue often appears when you’re using long expressions or messages that take more than a single line. In these cases, the parentheses are the natural way to format the code, and you may end up with something like the following:
number = 42
assert (
number > 0 and isinstance(number, int),
f"number greater than 0 expected, got: {number}"
)
Using a pair of parentheses to split a long line into multiple lines is a common formatting practice in Python code. However, in the context of an assert statement, the parentheses turn the assertion expression and message into a two-item tuple.
In practice, if you want to split a long assertion into several lines, then you can use the backslash character () for explicit line joining:
number = 42
assert number > 0 and isinstance(number, int),
f"number greater than 0 expected, got: {number}"
The backslash at the end of first line of this assertion joins the assertion’s two physical lines into a single logical line. This way, you can have appropriate line length without the risk of a warning or a logical error in your code.
There’s an edge case of this parentheses-related issue. If you provide only the assertion expression in parentheses, then assert will work just fine:
>>>
>>> number = 42
>>> assert(number > 0)
>>> number = -42
>>> assert(number > 0)
Traceback (most recent call last):
...
AssertionError
Why is this happening? To create a single-item tuple, you need to place a comma after the item itself. In the code above, the parentheses by themselves don’t create a tuple. That’s why the interpreter ignores the parentheses, and assert works as expected.
Even though the parentheses seem to work in the scenario described in the above example, it’s not a recommended practice. You can end up shooting yourself in the foot.
The AssertionError Exception
If the condition of an assert statement evaluates to false, then assert raises an AssertionError. If you provide the optional assertion message, then this message is internally used as an argument to the AssertionError class. Either way, the raised exception breaks your program’s execution.
Most of the time, you won’t raise AssertionError exceptions explicitly in your code. The assert statement takes care of raising this exception when the assertion condition fails. Additionally, you shouldn’t attempt to handle errors by writing code that catches the AssertionError exception, as you’ll learn later in this tutorial.
Finally, AssertionError is a built-in exception that inherits from the Exception class and is considered a concrete exception that should be raised instead of subclassed.
That’s it! Now you know the basics of the assert statement. You’ve learned the statement’s syntax, how assert works in practice, and also what the main characteristics of the AssertionError exception are. It’s time to move forward and explore some effective and common ways to write assertions in Python.
Exploring Common Assertion Formats
When it comes to writing the assert statement, you’ll find several assertion formats that are common in Python code. Being aware of these formats will allow you to write better assertions.
The following examples showcase a few of these common assertion formats, starting with assertions that compare objects:
>>>
>>> # Comparison assertions
>>> assert 3 > 2
>>> assert 3 == 2
Traceback (most recent call last):
...
AssertionError
>>> assert 3 > 2 and 5 < 10
>>> assert 3 == 2 or 5 > 10
Traceback (most recent call last):
...
AssertionError
Comparison assertions are intended to test conditions that compare two or more objects using comparison operators. These assertions can also include compound expressions based on Boolean operators.
Another common assertion format is related to membership tests:
>>>
>>> # Membership assertions
>>> numbers = [1, 2, 3, 4, 5]
>>> assert 4 in numbers
>>> assert 10 in numbers
Traceback (most recent call last):
...
AssertionError
Membership assertions allow you to check if a given item is present in a specific collection, such as a list, tuple, set, dictionary, or the like. These assertions use the membership operators, in and not in, to perform the required check.
The assertion format in the example below is related to an object’s identity:
>>>
>>> # Identity assertions
>>> x = 1
>>> y = x
>>> null = None
>>> assert x is y
>>> assert x is not y
Traceback (most recent call last):
...
AssertionError
>>> assert null is None
>>> assert null is not None
Traceback (most recent call last):
...
AssertionError
Identity assertions provide a way to test for an object’s identity. In this case, the assertion expression uses the identity operators, is and is not.
Finally, you’ll learn how to check the data type of your objects in the context of an assertion:
>>>
>>> # Type check assertions
>>> number = 42
>>> assert isinstance(number, int)
>>> number = 42.0
>>> assert isinstance(number, int)
Traceback (most recent call last):
...
AssertionError
Type check assertions commonly involve using the built-in isinstance() function to make sure that a given object is an instance of a certain class or classes.
Even though these are some of the most common assertion formats that you’ll find in Python code, there are many other possibilities. For example, you can use the built-in all() and any() functions to write assertions that check for the truth value of items in an iterable:
>>>
>>> assert all([True, True, True])
>>> assert all([True, False, True])
Traceback (most recent call last):
...
AssertionError
>>> assert any([False, True, False])
>>> assert any([False, False, False])
Traceback (most recent call last):
...
AssertionError
The all() assertions check if all the items in an input iterable are truthy, while the any() examples check if any item in the input iterable is truthy.
Your imagination is the only limit for writing useful assertions. You can write assertions using predicate or Boolean-valued functions, regular Python objects, comparison expressions, Boolean expressions, or general Python expressions. Your assertion will depend on what specific condition you need to check at a given moment.
Now you know some of the most common assertion formats that you can use in your code. It’s time to learn about specific use cases of assertions. In the following section, you’ll learn how to use assertions to document, debug, and test your code.
Documenting Your Code With Assertions
The assert statement is an effective way to document code. For example, if you want to state that a specific condition should always be true in your code, then assert condition can be better and more effective than a comment or a docstring, as you’ll learn in a moment.
To understand why assertions can be a handy documenting tool, say that you have a function that takes a server name and a tuple of port numbers. The function will iterate over the port numbers trying to connect to the target server. For your function to work correctly, the tuple of ports shouldn’t be empty:
def get_response(server, ports=(443, 80)):
# The ports argument expects a non-empty tuple
for port in ports:
if server.connect(port):
return server.get()
return None
If someone accidentally calls get_response() with an empty tuple, then the for loop will never run, and the function will return None even if the server is available. To alert programmers to this buggy call, you can use a comment, like you did in the example above. However, using an assert statement can be more effective:
def get_response(server, ports=(443, 80)):
assert len(ports) > 0, f"ports expected a non-empty tuple, got {ports}"
for port in ports:
if server.connect(port):
return server.get()
return None
The advantage of an assert statement over a comment is that when the condition isn’t true, assert immediately raises an AssertionError. After that, your code stops running, so it avoids abnormal behaviors and points you directly to the specific problem.
So, using assertions in situations like the one described above is an effective and powerful way to document your intentions and avoid hard-to-find bugs due to accidental errors or malicious actors.
Debugging Your Code With Assertions
At its core, the assert statement is a debugging aid for testing conditions that should remain true during your code’s normal execution. For assertions to work as a debugging tool, you should write them so that a failure indicates a bug in your code.
In this section, you’ll learn how to use the assert statement to assist you while debugging your code at development time.
An Example of Debugging With Assertions
You’ll typically use assertions to debug your code during development. The idea is to make sure that specific conditions are and remain true. If an asserted condition becomes false, then you immediately know that you have a bug.
As an example, say that you have the following Circle class:
# circle.py
import math
class Circle:
def __init__(self, radius):
if radius < 0:
raise ValueError("positive radius expected")
self.radius = radius
def area(self):
assert self.radius >= 0, "positive radius expected"
return math.pi * self.radius ** 2
The class’s initializer, .__init__(), takes radius as an argument and makes sure that the input value is a positive number. This check prevents circles with a negative radius.
The .area() method computes the circle’s area. However, before doing that, the method uses an assert statement to guarantee that .radius remains a positive number. Why would you add this check? Well, suppose that you’re working on a team, and one of your coworkers needs to add the following method to Circle:
class Circle:
# ...
def correct_radius(self, correction_coefficient):
self.radius *= correction_coefficient
This method takes a correction coefficient and applies it to the current value of .radius. However, the method doesn’t validate the coefficient, introducing a subtle bug. Can you spot it? Say that the user provides a negative correction coefficient by accident:
>>>
>>> from circle import Circle
>>> tire = Circle(42)
>>> tire.area()
5541.769440932395
>>> tire.correct_radius(-1.02)
>>> tire.radius
-42.84
>>> tire.area()
Traceback (most recent call last):
...
AssertionError: positive radius expected
The first call to .area() works correctly because the initial radius is positive. But the second call to .area() breaks your code with an AssertionError. Why? This happens because the call to .correct_radius() turns the radius into a negative number, which uncovers a bug: the function doesn’t properly check for valid input.
In this example, your assert statement works as a watchdog for situations in which the radius could take invalid values. The AssertionError immediately points you to the specific problem: .radius has unexpectedly changed to a negative number. You have to figure out how this unexpected change happened and then fix your code before it goes into production.
A Few Considerations on Debugging With Assertions
Developers often use assert statements to state preconditions, just like you did in the above example, where .area() checks for a valid .radius right before doing any computation. Developers also use assertions to state postconditions. For example, you can check if a function’s return value is valid, right before returning the value to the caller.
In general, the conditions that you check with an assert statement should be true, unless you or another developer in your team introduces a bug in the code. In other words, these conditions should never be false. Their purpose is to quickly flag if someone introduces a bug. In this regard, assertions are early alerts in your code. These alerts are meant to be useful during development.
If one of these conditions fails, then the program will crash with an AssertionError, telling you exactly which condition isn’t succeeding. This behavior will help you track down and fix bugs more quickly.
To properly use assertions as a debugging tool, you shouldn’t use try … except blocks that catch and handle AssertionError exceptions. If an assertion fails, then your program should crash because a condition that was supposed to be true became false. You shouldn’t change this intended behavior by catching the exception with a try … except block.
A proper use of assertions is to inform developers about unrecoverable errors in a program. Assertions shouldn’t signal an expected error, like a FileNotFoundError, where a user can take a corrective action and try again.
The goal of assertion should be to uncover programmers’ errors rather than users’ errors. Assertions are useful during the development process, not during production. By the time you release your code, it should be (mostly) free of bugs and shouldn’t require the assertions to work correctly.
Finally, once your code is ready for production, you don’t have to explicitly remove assertions. You can just disable them, as you’ll learn in the following section.
Disabling Assertions in Production for Performance
Now say that you’ve come to the end of your development cycle. Your code has been extensively reviewed and tested. All your assertions pass, and your code is ready for a new release. At this point, you can optimize the code for production by disabling the assertions that you added during development. Why should you optimize your code this way?
Assertions are great during development, but in production, they can affect the code’s performance. For example, a codebase with many assertions running all the time can be slower than the same code without assertions. Assertions take time to run, and they consume memory, so it’s advisable to disable them in production.
Now, how can you actually disable your assertions? Well, you have two options:
- Run Python with the
-Oor-OOoptions. - Set the
PYTHONOPTIMIZEenvironment variable to an appropriate value.
In this section, you’ll learn how to disable your assertions by using these two techniques. Before doing this, you’ll get to know the built-in __debug__ constant, which is the internal mechanism that Python uses to disable assertions.
Understanding the __debug__ Built-in Constant
Python has a built-in constant called __debug__. This constant is closely related to the assert statement. Python’s __debug__ is a Boolean constant, which defaults to True. It’s a constant because you can’t change its value once your Python interpreter is running:
>>>
>>> import builtins
>>> "__debug__" in dir(builtins)
True
>>> __debug__
True
>>> __debug__ = False
File "<stdin>", line 1
SyntaxError: cannot assign to __debug__
In this code snippet, you first confirm that __debug__ is a Python built-in that’s always available for you. True is the default value of __debug__, and there’s no way to change this value once your Python interpreter is running.
The value of __debug__ depends on which mode Python runs in, normal or optimized:
| Mode | Value of __debug__ |
|---|---|
| Normal (or debug) | True |
| Optimized | False |
Normal mode is typically the mode that you use during development, while optimized mode is what you should use in production. Now, what does __debug__ have to do with assertions? In Python, the assert statement is equivalent to the following conditional:
if __debug__:
if not expression:
raise AssertionError(assertion_message)
# Equivalent to
assert expression, assertion_message
If __debug__ is true, then the code under the outer if statement runs. The inner if statement checks expression for truthiness and raises an AssertionError only if the expression is not true. This is the default or normal Python mode, in which all your assertions are enabled because __debug__ is True.
On the other hand, if __debug__ is False, then the code under the outer if statement doesn’t run, meaning that your assertions will be disabled. In this case, Python is running in optimized mode.
Normal or debug mode allows you to have assertions in place as you develop and test the code. Once your current development cycle is complete, then you can switch to optimized mode and disable the assertions to get your code ready for production.
To activate optimized mode and disable your assertions, you can either start up the Python interpreter with the –O or -OO option, or set the system variable PYTHONOPTIMIZE to an appropriate value. You’ll learn how to do both operations in the following sections.
Running Python With the -O or -OO Options
You can disable all your assert statements by having the __debug__ constant set to False. To accomplish this task, you can use Python’s -O or -OO command-line options to run the interpreter in optimized mode.
The -O option internally sets __debug__ to False. This change removes the assert statements and any code that you’ve explicitly introduced under a conditional targeting __debug__. The -OO option does the same as -O and also discards docstrings.
Running Python with the -O or -OO command-line option makes your compiled bytecode smaller. Additionally, if you have several assertions or if __debug__: conditionals, then these command-line options can also make your code faster.
Now, what effect does this optimization have on your assertions? It disables them. For an example, open your command line or terminal within the directory containing the circle.py file and run an interactive session with the python -O command. Once there, run the following code:
>>>
>>> # Running Python in optimized mode
>>> __debug__
False
>>> from circle import Circle
>>> # Normal use of Circle
>>> ring = Circle(42)
>>> ring.correct_radius(1.02)
>>> ring.radius
42.84
>>> ring.area()
5765.656926346065
>>> # Invalid use of Circle
>>> ring = Circle(42)
>>> ring.correct_radius(-1.02)
>>> ring.radius
-42.84
>>> ring.area()
5765.656926346065
Because the -O option disables your assertions by setting __debug__ to False, your Circle class now accepts a negative radius, as the final example showcases. This behavior is completely wrong because you can’t have a circle with a negative radius. Additionaly, the circle’s area is computed using the wrong radius as an input.
The potential to disable assertions in optimized mode is the main reason why you must not use assert statements to validate input data but as an aid to your debugging and testing process.
A Pythonic solution for the Circle class would be to turn the .radius attribute into a managed attribute using the @property decorator. This way, you perform the .radius validation every time the attribute changes:
# circle.py
import math
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("positive radius expected")
self._radius = value
def area(self):
return math.pi * self.radius ** 2
def correct_radius(self, correction_coefficient):
self.radius *= correction_coefficient
Now .radius is a managed attribute that provides setter and getter methods using the @property decorator. You’ve moved the validation code from .__init__() to the setter method, which is called whenever the class changes the value of .radius.
Now, the updated Circle works as expected if you run the code in optimized mode:
>>>
>>> # Running Python in optimized mode
>>> __debug__
False
>>> from circle import Circle
>>> # Normal use of Circle
>>> ring = Circle(42)
>>> ring.correct_radius(1.02)
>>> ring.radius
42.84
>>> ring.area()
5765.656926346065
>>> # Invalid use of Circle
>>> ring = Circle(42)
>>> ring.correct_radius(-1.02)
Traceback (most recent call last):
...
ValueError: positive radius expected
Circle always validates the value of .radius before assignment, and your class works correctly, raising a ValueError for negative values of .radius. That’s it! You’ve fixed the bug with an elegant solution.
An interesting side effect of running Python in optimized mode is that code under an explicit if __debug__: condition is also disabled. Consider the following script:
# demo.py
print(f"{__debug__ = }")
if __debug__:
print("Running in Normal mode!")
else:
print("Running in Optimized mode!")
This script explicitly checks the value of __debug__ in an if … else statement. The code in the if code block will run only if __debug__ is True. In contrast, if __debug__ is False, then the code in the else block will run.
Now try running the script in normal and optimized mode to check its behavior in each mode:
$ python demo.py
__debug__ = True
Running in Normal mode!
$ python -O demo.py
__debug__ = False
Running in Optimized mode!
When you execute the script in normal mode, the code under the if __debug__: condition runs because __debug__ is True in this mode. On the other hand, when you execute the script in optimized mode with the -O option, __debug__ changes to False, and the code under the else block runs.
Python’s -O command-line option removes assertions from the resulting compiled bytecode. Python’s -OO option performs the same kind of optimization as -O, with the addition of removing docstrings from your bytecode.
Because both options set __debug__ to False, any code under an explicit if __debug__: conditional also stops working. This behavior provides a powerful mechanism to introduce debugging-only code in your Python projects during their development stages.
Now you know the basics of using Python’s -O and -OO options to disable your assertions in production code. However, running Python with either of these options every time you need to run your production code seems repetitive and may be error-prone. To automate the process, you can use the PYTHONOPTIMIZE environment variable.
Setting the PYTHONOPTIMIZE Environment Variable
You can also run Python in optimized mode with disabled assertions by setting the PYTHONOPTIMIZE environment variable to an appropriate value. For example, setting this variable to a non-empty string is equivalent to running Python with the -O option.
To try PYTHONOPTIMIZE out, fire up your command line and run the following command:
- Windows
- Linux + macOS
C:> set PYTHONOPTIMIZE="1"
$ export PYTHONOPTIMIZE="1"
Once you’ve set PYTHONOPTIMIZE to a non-empty string, you can launch your Python interpreter with the bare-bones python command. This command will automatically run Python in optimized mode.
Now go ahead and run the following code from the directory containing your circle.py file:
>>>
>>> from circle import Circle
>>> # Normal use of Circle
>>> ring = Circle(42)
>>> ring.correct_radius(1.02)
>>> ring.radius
42.84
>>> # Invalid use of Circle
>>> ring = Circle(42)
>>> ring.correct_radius(-1.02)
>>> ring.radius
-42.84
Again, your assertions are off, and the Circle class accepts negative radius values. You’re running Python in optimized mode again.
Another possibility is to set PYTHONOPTIMIZE to an integer value, n, which is equivalent to running Python using the -O option n times. In other words, you’re using n levels of optimization:
- Windows
- Linux + macOS
C:> set PYTHONOPTIMIZE=1 # Equivalent to python -O
C:> set PYTHONOPTIMIZE=2 # Equivalent to python -OO
$ export PYTHONOPTIMIZE=1 # Equivalent to python -O
$ export PYTHONOPTIMIZE=2 # Equivalent to python -OO
You can use any integer number to set PYTHONOPTIMIZE. However, Python only implements two levels of optimization. Using a number greater than 2 has no real effect on your compiled bytecode. Additionally, setting PYTHONOPTIMIZE to 0 will cause the interpreter to run in normal mode.
Running Python in Optimized Mode
When you run Python, the interpreter compiles any imported module to bytecode on the fly. The compiled bytecode will live in a directory called __pycache__/, which is placed in the directory containing the module that provided the imported code.
Inside __pycache__/, you’ll find a .pyc file named after your original module plus the interpreter’s name and version. The name of the .pyc file will also include the optimization level used to compile the code.
For example, when you import code from circle.py, the Python 3.10 interpreter generates the following files, depending on the optimization level:
| File Name | Command | PYTHONOPTIMIZE |
|---|---|---|
circle.cpython-310.pyc |
python circle.py |
0 |
circle.cpython-310.opt-1.pyc |
python -O circle.py |
1 |
circle.cpython-310.opt-2.pyc |
python -OO circle.py |
2 |
The name of each file in this table includes the original module’s name (circle), the interpreter that generated the code (cpython-310), and the optimization level (opt-x). The table also summarizes the corresponding commands and values for the PYTHONOPTIMIZE variable. PEP 488 provides more context on this naming format for .pyc files.
The main results of running Python in the first level of optimization is that the interpreter sets __debug__ to False and removes the assertions from the resulting compiled bytecode. These optimizations make the code smaller and potentially faster than the same code running in normal mode.
The second level of optimization does the same as the first level. It also removes all the docstrings from the compiled code, which results in an even smaller compiled bytecode.
Testing Your Code With Assertions
Testing is another field in the development process where assertions are useful. Testing boils down to comparing an observed value with an expected one to check if they’re equal or not. This kind of check perfectly fits into assertions.
Assertions must check for conditions that should typically be true, unless you have a bug in your code. This idea is another important concept behind testing.
The pytest third-party library is a popular testing framework in Python. At its core, you’ll find the assert statement, which you can use to write most of your test cases in pytest.
Here are a few examples of writing test cases using assert statements. The examples below take advantage of some built-in functions, which provide the testing material:
# test_samples.py
def test_sum():
assert sum([1, 2, 3]) == 6
def test_len():
assert len([1, 2, 3]) > 0
def test_reversed():
assert list(reversed([1, 2, 3])) == [3, 2, 1]
def test_membership():
assert 3 in [1, 2, 3]
def test_isinstance():
assert isinstance([1, 2, 3], list)
def test_all():
assert all([True, True, True])
def test_any():
assert any([False, True, False])
def test_always_fail():
assert pow(10, 2) == 42
All these test cases use the assert statement. Most of them are written using the assertion formats that you learned before. They all showcase how you’d write real-world test cases to check different pieces of your code with pytest.
Now, why does pytest favor plain assert statements in test cases over a custom API, which is what other testing frameworks prefer? There are a couple of remarkable advantages behind this choice:
- The
assertstatement allowspytestto lower the entry barrier and somewhat flatten the learning curve because its users can take advantage of Python syntax that they already know. - The users of
pytestdon’t need to import anything from the library to start writing test cases. They only need to start importing things if their test cases get complicated, demanding more advanced features.
These advantages make working with pytest a pleasant experience for beginners and people coming from other testing frameworks with custom APIs.
For example, the standard-library unittest module provides an API consisting of a list of .assert*() methods that work pretty much like assert statements. This kind of API can be difficult to learn and memorize for developers starting with the framework.
You can use pytest to run all the test case examples above. First, you need to install the library by issuing the python -m pip install pytest command. Then you can execute pytest test_samples.py from the command-line. This latter command will display an output similar to the following:
========================== test session starts =========================
platform linux -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /home/user/python-assert
collected 8 items
test_samples.py .......F [100%]
========================== FAILURES =====================================
__________________________ test_always_fail _____________________________
def test_always_fail():
> assert pow(10, 2) == 42
E assert 100 == 42
E + where 100 = pow(10, 2)
test_samples.py:25: AssertionError
========================== short test summary info ======================
FAILED test_samples.py::test_always_fail - assert 100 == 42
========================== 1 failed, 7 passed in 0.21s ==================
The first highlighted line in this output tells you that pytest discovered and ran eight test cases. The second highlighted line shows that seven out of eight tests passed successfully. That’s why you get seven green dots and an F.
A remarkable feature to note is that pytest integrates nicely with the assert statement. The library can display error reports with detailed information about the failing assertions and why they’re failing. As an example, check out the the lines starting with the E letter in the above output. They display error messages.
Those lines clearly uncover the root cause of the failure. In this example, pow(10, 2) returns 100 instead of 42, which is intentionally wrong. You can use pytest.raises() to handle code that is expected to fail.
Understanding Common Pitfalls of assert
Even though assertions are such a great and useful tool, they have some downsides. Like any other tool, assertions can be misused. You’ve learned that you should use assertions mainly for debugging and testing code during development. In contrast, you shouldn’t rely on assertions to provide functionality in production code, which is one of the main drivers of pitfalls with assertions.
In particular, you may run into pitfalls if you use assertions for:
- Processing and validating data
- Handling errors
- Running operations with side effects
Another common source of issues with assertions is that keeping them enabled in production can negatively impact your code’s performance.
Finally, Python has assertions enabled by default, which can confuse developers coming from other languages. In the following sections, you’ll learn about all these possible pitfalls of assertions. You’ll also learn how to avoid them in your own Python code.
Using assert for Data Processing and Validation
You shouldn’t use assert statements to verify the user’s input or any other input data from external sources. That’s because production code typically disables assertions, which will remove all the verification.
For example, suppose you’re building an online store with Python, and you need to add functionality to accept discount coupons. You end up writing the following function:
# store.py
# Code under development
def price_with_discount(product, discount):
assert 0 < discount < 1, "discount expects a value between 0 and 1"
new_price = int(product["price"] * (1 - discount))
return new_price
Notice the assert statement in the first line of price_with_discount()? It’s there to guarantee that the discounted price won’t be equal to or lower than zero dollars. The assertion also ensures that the new price won’t be higher than the product’s original price.
Now consider the example of a pair of shoes at twenty-five percent off:
>>>
>>> from store import price_with_discount
>>> shoes = {"name": "Fancy Shoes", "price": 14900}
>>> # 25% off -> $111.75
>>> price_with_discount(shoes, 0.25)
11175
All right, price_with_discount() works nicely! It takes the product as a dictionary, applies the intended discount to the current price, and returns the new price. Now, try to apply some invalid discounts:
>>>
>>> # 200% off
>>> price_with_discount(shoes, 2.0)
Traceback (most recent call last):
...
AssertionError: discount expects a value between 0 and 1
>>> # 100% off
>>> price_with_discount(shoes, 1)
Traceback (most recent call last):
...
AssertionError: discount expects a value between 0 and 1
Applying an invalid discount raises an AssertionError that points out the violated condition. If you ever encounter this error while developing and testing your online store, then it shouldn’t be hard to figure out what happened by looking at the traceback.
The real problem with the example above comes if the end user can make direct calls to price_with_discount() in production code with disabled assertions. In this situation, the function won’t check the input value for discount, possibly accepting wrong values and breaking the correctness of your discount functionality.
In general, you can write assert statements to process, validate, or verify data during development. However, if those operations remain valid in production code, then make sure to replace them with an if statement or a try … except block.
Here’s a new version of price_with_discount() that uses a conditional instead of an assertion:
# store.py
# Code in production
def price_with_discount(product, discount):
if 0 < discount < 1:
new_price = int(product["price"] * (1 - discount))
return new_price
raise ValueError("discount expects a value between 0 and 1")
In this new implementation of price_with_discount(), you replace the assert statement with an explicit conditional statement. The function now applies the discount only if the input value is between 0 and 1. Otherwise, it raises a ValueError, signaling the problem.
Now you can wrap up any calls to this function in a try … except block that catches the ValueError and sends an informative message to the users so that they can take action accordingly.
The moral of this example is that you shouldn’t rely on the assert statement for data processing or data validation, because this statement is typically turned off in production code.
Handling Errors With assert
Another important pitfall with assertions is that sometimes developers use them as a quick form of error handling. As a result, if the production code removes assertions, then important error checks are also removed from the code. So, keep in mind that assertions aren’t a replacement for good error handling.
Here’s an example of using assertions for error handling:
# Bad practice
def square(x):
assert x >= 0, "only positive numbers are allowed"
return x ** 2
try:
square(-2)
except AssertionError as error:
print(error)
If you execute this code in production with disabled assertions, then square() will never run the assert statement and raise an AssertionError. In this situation, the try … except block is superfluous and nonfunctional.
What can you do to fix this example? Try updating square() to use an if statement and a ValueError:
# Best practice
def square(x):
if x < 0:
raise ValueError("only positive numbers are allowed")
return x ** 2
try:
square(-2)
except ValueError as error:
print(error)
Now square() deals with the condition by using an explicit if statement that can’t be disabled in production code. Your try … except block now handles a ValueError, which is a more appropriate exception in this example.
Don’t ever catch AssertionError exceptions in your code, because that would silence failing assertions, which is a clear sign of misused assertions. Instead, catch concrete exceptions that are clearly related to the errors that you’re handling and let your assertions fail.
Use assertions only to check errors that shouldn’t happen during the normal execution of your programs unless you have a bug. Remember that assertions can be disabled.
Running assert on Expressions With Side Effects
Another subtle pitfall with the assert statement appears when you use this statement to check operations, functions, or expressions that have some kind of side effect. In other words, these operations modify the state of objects outside the operation’s scope.
In those situations, the side effect takes place every time your code runs the assertion, which might silently change your program’s global state and behavior.
Consider the following toy example, in which a function modifies the value of a global variable as a side effect:
>>>
>>> sample = [42, 27, 40, 38]
>>> def popped(sample, index=-1):
... item = sample.pop(index)
... return item
...
>>> assert sample[-1] == popped(sample)
>>> assert sample[1] == popped(sample, 1)
>>> sample
[42, 40]
In this example, popped() returns item at a given index in the input sample of data, with the side effect of also removing said item.
Using assertions to make sure that your function is returning the correct item can seem appropriate. However, this will cause the function’s internal side effect to run in every assertion, modifying the original content of sample.
To prevent unexpected behaviors like the one in the above example, use assertion expressions that don’t cause side effects. For example, you can use pure functions that just take input arguments and return the corresponding output without modifying the state of objects from other scopes and namespaces.
Impacting Performance With assert
Too many assertions in production can impact your code’s performance. This issue becomes critical when the asserted conditions involve too much logic, such as long compound conditions, long-running predicate functions, and classes implying a costly instantiation process.
Assertions can impact your code’s performance in two main ways. They will:
- Take time to execute
- Use extra memory
An assert statement that checks for a None value can be relatively inexpensive. However, more complex assertions, especially those running heavy code, can measurably slow down your code. Assertions also consume memory to store their own code and any required data.
To avoid performance issues in production code, you should use Python’s -O or -OO command-line options or set the PYTHONOPTIMIZE environment variable according to your needs. Either strategy will optimize your code by generating an assertions-free compiled bytecode, which will run more quickly and take up less memory.
Additionally, to prevent performance issues during development, your assertions should be fairly slim and to the point.
Having assert Statements Enabled by Default
In Python, assertions are enabled by default. When the interpreter runs in normal mode, the __debug__ variable is True, and your assertions are enabled. This behavior makes sense because you typically develop, debug, and test your code in normal mode.
If you want to disable your assertions, then you need to do it explicitly. You can either run the Python interpreter with the -o or -OO options, or set the PYTHONOPTIMIZE environment variable to a proper value.
In contrast, other programming languages have assertions disabled by default. For example, if you’re coming into Python from Java, you may assume that your assertions won’t run unless you explicitly turn them on. This assumption can be a common source of confusion for Python beginners, so keep it in mind.
Conclusion
Now you know how to use Python’s assert statement to set sanity checks throughout your code and make sure that certain conditions are and remain true. When any of these conditions fail, you have a clear indication of what’s happening. This way, you can quickly debug and fix your code.
The assert statement is pretty handy when you need to document, debug, and test your code during the development stages. In this tutorial, you learned how to use assertions in your code and how they can make your debugging and testing process more efficient and straightforward.
In this tutorial, you learned:
- What assertions are and when to use them
- How Python’s
assertstatement works - How
assertis handy for documenting, debugging, and testing code - How assertions can be disabled to improve performance in production
- What common pitfalls you can face when using
assertstatements
With this knowledge on the assert statement, you can now write robust, reliable, and less buggy code, which can take you to the next level as a developer.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Using Python’s assert to Debug and Test Your Code
Overview
An Assertion is a programming concept that helps us to test an expression. If the expression returns true, the control of the program is moved to the next line. But if the expression returns false, an AssertionError is generated by Python. AssertionError Python can be avoided by using the -O flag, using the environment variable to set a flag, or by making sure that the flow of the program doesn’t reach the assert statement. AssertionError Python is useful to check the output of a function, to check whether the input is valid or not, or to test a program.
What is Assertionerror in Python?
Before learning about AssertionError Python, let’s have a brief of what is an assert statement. The assert statement is used when we want to debug the code. Consider a case where we want the expression ‘x’ to be always true. But can we check if ‘x’ is always true or not? Here comes the assert statement. An assert statement returns the AssertionError if the expression is false. Hence it halts the execution of the program.
Explanation
From the above image, we can see that if the assertion condition is false, the program is halted and AssertionError Python is returned.
Syntax
The assert statement in Python also provides us the option to add an error message in case the AssertionError Python is returned. Let’s see the syntax for both cases.
Without Error Message
With Error Message
Examples
Let’s have an example of the division of two numbers. When we divide a number x by another y, we know that y can not be equal to 0. Hence we have to make sure that y!=0. Here our expression will be y!=0 if it returns true we must stop the execution of the program. Let’s use the assert statement to implement the same.
Output:
Now let’s set y as 0 and check the program’s output.
Output:
See the assert statement returned the AssertionError Python as y was equal to 0.
Now let’s add the error-message to the assert statement and then check the program’s output.
Output:
Here we can see that the error-message is printed stating that we «Cannot divide by 0».
How does Assertionerror in Python Work?
The Python programming language has an assert statement that allows us to generate basic debug message outputs based on logical assertions. An AssertionError Python is returned when the assertion expression is false. In Python BaseException class is the base of all the exception classes. AssertionError’s base class is the Exception class which inherits from the BaseException class. When an assert statement is used in the code, we have to make sure to add the exception handling code as well. If the error-message is provided in the code, the error-message is printed else the error is handled without the error-message.
Steps to Avoid Assertionerror in Python
- The -O flag can be used to disable all assert statements in the program. By disabling the assert statements in the program, we can avoid the AssertionError exception in Python.
Example: If we try to run python -c «assert False» in the terminal, the output states that there’s an assertion error as the expression is false. But we can use the -O to disable the assert statement as,The output of the above code in the terminal will be empty. As the assert statement was disabled by the -O flag.
- We can also use the environment variable to set a flag that disables the assert statements. But note, that all the processes that inherit or use this environment will be impacted if this is used.
Example: In the cmd we can use SET PYTHONOPTIMIZE=TRUE to disable the assert statement and this will affect all the processes that use or inherit the environment. Later we can enable asserts using SET PYTHONOPTIMIZE=. - Another way to avoid AssertionError Python is to make sure that the flow of the program doesn’t reach the assert statement or after catching the AssertionError Python the normal flow of the program should resume. For example. using try-except block in Python.
Handling Python Assertionerror
Consider the example we used in the beginning of the article where we are dividing two numbers. We will add a try-except block to that code and let’s see the output.
Output:
Can you guess why the output is empty?
This is because the except block is printing the error-message but we have not provided any error-message to the assert statement. Let’s add the error-message and check the output.
Output:
Practical Applications of Python Assertion Error
- Checking values of parameters
We can check whether the provided parameters are valid or not. Just like the example we saw in the beginning of the article. - Checking valid input/type
We can also check whether the user has passed the correct data type or not. Consider the case where we are dividing two numbers and the user passes string instead of numbers. This can be caught using the assert statement. - We can also detect whether an interface is being misused by another programmer or not.
Assertionerror Python Example
Let’s have an example to calculate average marks of a student,
Output:
You can see in the above program even after getting an AssertionError Python on line 10, the normal flow of the program resumes.
Conclusion
- The Python programming language has an assert statement that allows us to generate basic debug message outputs based on logical assertions.
- An AssertionError Python is returned when the assertion expression is false.
- When an assert statement is used in the code, we have to make sure to add the exception handling code as well.
- AssertionError Python can be avoided by using the -O flag, using the environment variable to set a flag, or by making sure that the flow of the program doesn’t reach the assert statement.
The unittest unit testing framework is used to validate that the code performs as designed. To achieve this, unittest supports some important methods in an object-oriented way:
- test fixture
- test case
- test suite
- test runner
A deeper insight for the above terms can be gained from https://www.geeksforgeeks.org/unit-testing-python-unittest/
assertRaises() – It allows an exception to be encapsulated, meaning that the test can throw an exception without exiting the execution, as is normally the case for unhandled exceptions. The test passes if exception is raised, gives an error if another exception is raised, or fails if no exception is raised.
There are two ways you can use assertRaises:
- using keyword arguments.
assertRaises(exception, function, *args, **keywords)
Just pass the exception, the callable function and the parameters of the callable function as keyword arguments that will elicit the exception.
- using context manager
assertRaises(exception)
Make a function call that should raise the exception with a context. The context manager will caught an exception and store it in the object in its exception attribute. This is useful when we have to perform additional checks on the exception raised.
We write a unittest that fails if no exception is raised by a function or when an exception raised by assert statement is different from expected exception. Thus, by this method we can check if an exception is raised by a function or not.
Example 1 :
Python3
import unittest
class MyTestCase(unittest.TestCase):
def test_1(self):
with self.assertRaises(Exception):
1 + '1'
if __name__ == '__main__':
unittest.main()
Output :
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Here, in the output the “.” on the first line of output means that test has passed and the function has thrown an exception of TypeError while adding an integer value and a string value.
Example 2 :
Python3
import unittest
class MyTestCase(unittest.TestCase):
def test_1(self):
with self.assertRaises(Exception):
1 + 1
if __name__ == '__main__':
unittest.main()
Output :
F
======================================================================
FAIL: test_1 (__main__.MyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/5bc65171e57a3294596f5333f4b7ed53.py", line 6, in test_1
1 + 1
AssertionError: Exception not raised
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Since adding an integer to an integer (in this case 1 + 1) does not raise any exception, results in the unittest to fail.
Example 3 :
Python3
import unittest
class MyTestCase(unittest.TestCase):
def test_1(self):
with self.assertRaises(ZeroDivisionError):
100 / 0
if __name__ == '__main__':
unittest.main()
Output :
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Any number divided by 0 gives ZeroDivisionError exception. Therefore, in example 3, when 100 is divided by 0 it raises an Exception of type ZeroDivisionError resulting in unittest to pass. Thus “.” in output signifies that the test has been passed.
Example 4 :
Python3
import unittest
class MyTestCase(unittest.TestCase):
def test_1(self):
with self.assertRaises(RuntimeError):
file = open("GeeksforGeeks.txt", 'r')
if __name__ == '__main__':
unittest.main()
Output :
E
======================================================================
ERROR: test_1 (__main__.MyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/8520c06939bb0023b4f76f381b2c8cf2.py", line 11, in test_1
file = open("GeeksforGeeks.txt", 'r')
FileNotFoundError: [Errno 2] No such file or directory: 'GeeksforGeeks.txt'
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
When we try to open a file that does not exists, it raises an IOError which is sub class of EnvironmentError. In the above example, the unittest failed because the type of exception to be raised by function was expected to be of type RuntimeError, instead it raised an exception of EnvironmentError. Since the exception raised and exception expected are different, it produces an error.
Last Updated :
20 Aug, 2020
Like Article
Save Article
Обработка ошибок увеличивает отказоустойчивость кода, защищая его от потенциальных сбоев, которые могут привести к преждевременному завершению работы.
From the above image, we can see that if the assertion condition is false, the program is halted and AssertionError Python is returned.
Syntax
The assert statement in Python also provides us the option to add an error message in case the AssertionError Python is returned. Let’s see the syntax for both cases.
Without Error Message
With Error Message
Examples
Let’s have an example of the division of two numbers. When we divide a number x by another y, we know that y can not be equal to 0. Hence we have to make sure that y!=0. Here our expression will be y!=0 if it returns true we must stop the execution of the program. Let’s use the assert statement to implement the same.
Output:
Now let’s set y as 0 and check the program’s output.
Output:
See the assert statement returned the AssertionError Python as y was equal to 0.
Now let’s add the error-message to the assert statement and then check the program’s output.
Output:
Here we can see that the error-message is printed stating that we «Cannot divide by 0».
How does Assertionerror in Python Work?
The Python programming language has an assert statement that allows us to generate basic debug message outputs based on logical assertions. An AssertionError Python is returned when the assertion expression is false. In Python BaseException class is the base of all the exception classes. AssertionError’s base class is the Exception class which inherits from the BaseException class. When an assert statement is used in the code, we have to make sure to add the exception handling code as well. If the error-message is provided in the code, the error-message is printed else the error is handled without the error-message.
Steps to Avoid Assertionerror in Python
- The -O flag can be used to disable all assert statements in the program. By disabling the assert statements in the program, we can avoid the AssertionError exception in Python.
Example: If we try to run python -c «assert False» in the terminal, the output states that there’s an assertion error as the expression is false. But we can use the -O to disable the assert statement as,The output of the above code in the terminal will be empty. As the assert statement was disabled by the -O flag.
- We can also use the environment variable to set a flag that disables the assert statements. But note, that all the processes that inherit or use this environment will be impacted if this is used.
Example: In the cmd we can use SET PYTHONOPTIMIZE=TRUE to disable the assert statement and this will affect all the processes that use or inherit the environment. Later we can enable asserts using SET PYTHONOPTIMIZE=. - Another way to avoid AssertionError Python is to make sure that the flow of the program doesn’t reach the assert statement or after catching the AssertionError Python the normal flow of the program should resume. For example. using try-except block in Python.
Handling Python Assertionerror
Consider the example we used in the beginning of the article where we are dividing two numbers. We will add a try-except block to that code and let’s see the output.
Output:
Can you guess why the output is empty?
This is because the except block is printing the error-message but we have not provided any error-message to the assert statement. Let’s add the error-message and check the output.
Output:
Practical Applications of Python Assertion Error
- Checking values of parameters
We can check whether the provided parameters are valid or not. Just like the example we saw in the beginning of the article. - Checking valid input/type
We can also check whether the user has passed the correct data type or not. Consider the case where we are dividing two numbers and the user passes string instead of numbers. This can be caught using the assert statement. - We can also detect whether an interface is being misused by another programmer or not.
Assertionerror Python Example
Let’s have an example to calculate average marks of a student,
Output:
You can see in the above program even after getting an AssertionError Python on line 10, the normal flow of the program resumes.
Conclusion
- The Python programming language has an assert statement that allows us to generate basic debug message outputs based on logical assertions.
- An AssertionError Python is returned when the assertion expression is false.
- When an assert statement is used in the code, we have to make sure to add the exception handling code as well.
- AssertionError Python can be avoided by using the -O flag, using the environment variable to set a flag, or by making sure that the flow of the program doesn’t reach the assert statement.
The unittest unit testing framework is used to validate that the code performs as designed. To achieve this, unittest supports some important methods in an object-oriented way:
- test fixture
- test case
- test suite
- test runner
A deeper insight for the above terms can be gained from https://www.geeksforgeeks.org/unit-testing-python-unittest/
assertRaises() – It allows an exception to be encapsulated, meaning that the test can throw an exception without exiting the execution, as is normally the case for unhandled exceptions. The test passes if exception is raised, gives an error if another exception is raised, or fails if no exception is raised.
There are two ways you can use assertRaises:
- using keyword arguments.
assertRaises(exception, function, *args, **keywords)
Just pass the exception, the callable function and the parameters of the callable function as keyword arguments that will elicit the exception.
- using context manager
assertRaises(exception)
Make a function call that should raise the exception with a context. The context manager will caught an exception and store it in the object in its exception attribute. This is useful when we have to perform additional checks on the exception raised.
We write a unittest that fails if no exception is raised by a function or when an exception raised by assert statement is different from expected exception. Thus, by this method we can check if an exception is raised by a function or not.
Example 1 :
Python3
import unittest
class MyTestCase(unittest.TestCase):
def test_1(self):
with self.assertRaises(Exception):
1 + '1'
if __name__ == '__main__':
unittest.main()
Output :
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Here, in the output the “.” on the first line of output means that test has passed and the function has thrown an exception of TypeError while adding an integer value and a string value.
Example 2 :
Python3
import unittest
class MyTestCase(unittest.TestCase):
def test_1(self):
with self.assertRaises(Exception):
1 + 1
if __name__ == '__main__':
unittest.main()
Output :
F
======================================================================
FAIL: test_1 (__main__.MyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/5bc65171e57a3294596f5333f4b7ed53.py", line 6, in test_1
1 + 1
AssertionError: Exception not raised
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Since adding an integer to an integer (in this case 1 + 1) does not raise any exception, results in the unittest to fail.
Example 3 :
Python3
import unittest
class MyTestCase(unittest.TestCase):
def test_1(self):
with self.assertRaises(ZeroDivisionError):
100 / 0
if __name__ == '__main__':
unittest.main()
Output :
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Any number divided by 0 gives ZeroDivisionError exception. Therefore, in example 3, when 100 is divided by 0 it raises an Exception of type ZeroDivisionError resulting in unittest to pass. Thus “.” in output signifies that the test has been passed.
Example 4 :
Python3
import unittest
class MyTestCase(unittest.TestCase):
def test_1(self):
with self.assertRaises(RuntimeError):
file = open("GeeksforGeeks.txt", 'r')
if __name__ == '__main__':
unittest.main()
Output :
E
======================================================================
ERROR: test_1 (__main__.MyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/8520c06939bb0023b4f76f381b2c8cf2.py", line 11, in test_1
file = open("GeeksforGeeks.txt", 'r')
FileNotFoundError: [Errno 2] No such file or directory: 'GeeksforGeeks.txt'
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
When we try to open a file that does not exists, it raises an IOError which is sub class of EnvironmentError. In the above example, the unittest failed because the type of exception to be raised by function was expected to be of type RuntimeError, instead it raised an exception of EnvironmentError. Since the exception raised and exception expected are different, it produces an error.
Last Updated :
20 Aug, 2020
Like Article
Save Article
Обработка ошибок увеличивает отказоустойчивость кода, защищая его от потенциальных сбоев, которые могут привести к преждевременному завершению работы.
Прежде чем переходить к обсуждению того, почему обработка исключений так важна, и рассматривать встроенные в Python исключения, важно понять, что есть тонкая грань между понятиями ошибки и исключения.
Ошибку нельзя обработать, а исключения Python обрабатываются при выполнении программы. Ошибка может быть синтаксической, но существует и много видов исключений, которые возникают при выполнении и не останавливают программу сразу же. Ошибка может указывать на критические проблемы, которые приложение и не должно перехватывать, а исключения — состояния, которые стоит попробовать перехватить. Ошибки — вид непроверяемых и невозвратимых ошибок, таких как OutOfMemoryError, которые не стоит пытаться обработать.
Обработка исключений делает код более отказоустойчивым и помогает предотвращать потенциальные проблемы, которые могут привести к преждевременной остановке выполнения. Представьте код, который готов к развертыванию, но все равно прекращает работу из-за исключения. Клиент такой не примет, поэтому стоит заранее обработать конкретные исключения, чтобы избежать неразберихи.
Ошибки могут быть разных видов:
- Синтаксические
- Недостаточно памяти
- Ошибки рекурсии
- Исключения
Разберем их по очереди.
Синтаксические ошибки (SyntaxError)
Синтаксические ошибки часто называют ошибками разбора. Они возникают, когда интерпретатор обнаруживает синтаксическую проблему в коде.
Рассмотрим на примере.
a = 8
b = 10
c = a b
File "", line 3
c = a b
^
SyntaxError: invalid syntax
Стрелка вверху указывает на место, где интерпретатор получил ошибку при попытке исполнения. Знак перед стрелкой указывает на причину проблемы. Для устранения таких фундаментальных ошибок Python будет делать большую часть работы за программиста, выводя название файла и номер строки, где была обнаружена ошибка.
Недостаточно памяти (OutofMemoryError)
Ошибки памяти чаще всего связаны с оперативной памятью компьютера и относятся к структуре данных под названием “Куча” (heap). Если есть крупные объекты (или) ссылки на подобные, то с большой долей вероятности возникнет ошибка OutofMemory. Она может появиться по нескольким причинам:
- Использование 32-битной архитектуры Python (максимальный объем выделенной памяти невысокий, между 2 и 4 ГБ);
- Загрузка файла большого размера;
- Запуск модели машинного обучения/глубокого обучения и много другое;
Обработать ошибку памяти можно с помощью обработки исключений — резервного исключения. Оно используется, когда у интерпретатора заканчивается память и он должен немедленно остановить текущее исполнение. В редких случаях Python вызывает OutofMemoryError, позволяя скрипту каким-то образом перехватить самого себя, остановить ошибку памяти и восстановиться.
Но поскольку Python использует архитектуру управления памятью из языка C (функция malloc()), не факт, что все процессы восстановятся — в некоторых случаях MemoryError приведет к остановке. Следовательно, обрабатывать такие ошибки не рекомендуется, и это не считается хорошей практикой.
Ошибка рекурсии (RecursionError)
Эта ошибка связана со стеком и происходит при вызове функций. Как и предполагает название, ошибка рекурсии возникает, когда внутри друг друга исполняется много методов (один из которых — с бесконечной рекурсией), но это ограничено размером стека.
Все локальные переменные и методы размещаются в стеке. Для каждого вызова метода создается стековый кадр (фрейм), внутрь которого помещаются данные переменной или результат вызова метода. Когда исполнение метода завершается, его элемент удаляется.
Чтобы воспроизвести эту ошибку, определим функцию recursion, которая будет рекурсивной — вызывать сама себя в бесконечном цикле. В результате появится ошибка StackOverflow или ошибка рекурсии, потому что стековый кадр будет заполняться данными метода из каждого вызова, но они не будут освобождаться.
def recursion():
return recursion()
recursion()
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
in
----> 1 recursion()
in recursion()
1 def recursion():
----> 2 return recursion()
... last 1 frames repeated, from the frame below ...
in recursion()
1 def recursion():
----> 2 return recursion()
RecursionError: maximum recursion depth exceeded
Ошибка отступа (IndentationError)
Эта ошибка похожа по духу на синтаксическую и является ее подвидом. Тем не менее она возникает только в случае проблем с отступами.
Пример:
for i in range(10):
print('Привет Мир!')
File "", line 2
print('Привет Мир!')
^
IndentationError: expected an indented block
Исключения
Даже если синтаксис в инструкции или само выражение верны, они все равно могут вызывать ошибки при исполнении. Исключения Python — это ошибки, обнаруживаемые при исполнении, но не являющиеся критическими. Скоро вы узнаете, как справляться с ними в программах Python. Объект исключения создается при вызове исключения Python. Если скрипт не обрабатывает исключение явно, программа будет остановлена принудительно.
Программы обычно не обрабатывают исключения, что приводит к подобным сообщениям об ошибке:
Ошибка типа (TypeError)
a = 2
b = 'PythonRu'
a + b
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in
1 a = 2
2 b = 'PythonRu'
----> 3 a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Ошибка деления на ноль (ZeroDivisionError)
10 / 0
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
in
----> 1 10 / 0
ZeroDivisionError: division by zero
Есть разные типы исключений в Python и их тип выводится в сообщении: вверху примеры TypeError и ZeroDivisionError. Обе строки в сообщениях об ошибке представляют собой имена встроенных исключений Python.
Оставшаяся часть строки с ошибкой предлагает подробности о причине ошибки на основе ее типа.
Теперь рассмотрим встроенные исключения Python.
Встроенные исключения
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
| +-- ModuleNotFoundError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- MemoryError
+-- NameError
| +-- UnboundLocalError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
+-- ReferenceError
+-- RuntimeError
| +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning
Прежде чем переходить к разбору встроенных исключений быстро вспомним 4 основных компонента обработки исключения, как показано на этой схеме.
Try: он запускает блок кода, в котором ожидается ошибка.Except: здесь определяется тип исключения, который ожидается в блокеtry(встроенный или созданный).Else: если исключений нет, тогда исполняется этот блок (его можно воспринимать как средство для запуска кода в том случае, если ожидается, что часть кода приведет к исключению).Finally: вне зависимости от того, будет ли исключение или нет, этот блок кода исполняется всегда.
В следующем разделе руководства больше узнаете об общих типах исключений и научитесь обрабатывать их с помощью инструмента обработки исключения.
Ошибка прерывания с клавиатуры (KeyboardInterrupt)
Исключение KeyboardInterrupt вызывается при попытке остановить программу с помощью сочетания Ctrl + C или Ctrl + Z в командной строке или ядре в Jupyter Notebook. Иногда это происходит неумышленно и подобная обработка поможет избежать подобных ситуаций.
В примере ниже если запустить ячейку и прервать ядро, программа вызовет исключение KeyboardInterrupt. Теперь обработаем исключение KeyboardInterrupt.
try:
inp = input()
print('Нажмите Ctrl+C и прервите Kernel:')
except KeyboardInterrupt:
print('Исключение KeyboardInterrupt')
else:
print('Исключений не произошло')
Исключение KeyboardInterrupt
Стандартные ошибки (StandardError)
Рассмотрим некоторые базовые ошибки в программировании.
Арифметические ошибки (ArithmeticError)
- Ошибка деления на ноль (Zero Division);
- Ошибка переполнения (OverFlow);
- Ошибка плавающей точки (Floating Point);
Все перечисленные выше исключения относятся к классу Arithmetic и вызываются при ошибках в арифметических операциях.
Деление на ноль (ZeroDivisionError)
Когда делитель (второй аргумент операции деления) или знаменатель равны нулю, тогда результатом будет ошибка деления на ноль.
try:
a = 100 / 0
print(a)
except ZeroDivisionError:
print("Исключение ZeroDivisionError." )
else:
print("Успех, нет ошибок!")
Исключение ZeroDivisionError.
Переполнение (OverflowError)
Ошибка переполнение вызывается, когда результат операции выходил за пределы диапазона. Она характерна для целых чисел вне диапазона.
try:
import math
print(math.exp(1000))
except OverflowError:
print("Исключение OverFlow.")
else:
print("Успех, нет ошибок!")
Исключение OverFlow.
Ошибка утверждения (AssertionError)
Когда инструкция утверждения не верна, вызывается ошибка утверждения.
Рассмотрим пример. Предположим, есть две переменные: a и b. Их нужно сравнить. Чтобы проверить, равны ли они, необходимо использовать ключевое слово assert, что приведет к вызову исключения Assertion в том случае, если выражение будет ложным.
try:
a = 100
b = "PythonRu"
assert a == b
except AssertionError:
print("Исключение AssertionError.")
else:
print("Успех, нет ошибок!")
Исключение AssertionError.
Ошибка атрибута (AttributeError)
При попытке сослаться на несуществующий атрибут программа вернет ошибку атрибута. В следующем примере можно увидеть, что у объекта класса Attributes нет атрибута с именем attribute.
class Attributes(obj):
a = 2
print(a)
try:
obj = Attributes()
print(obj.attribute)
except AttributeError:
print("Исключение AttributeError.")
2
Исключение AttributeError.
Ошибка импорта (ModuleNotFoundError)
Ошибка импорта вызывается при попытке импортировать несуществующий (или неспособный загрузиться) модуль в стандартном пути или даже при допущенной ошибке в имени.
import nibabel
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
in
----> 1 import nibabel
ModuleNotFoundError: No module named 'nibabel'
Ошибка поиска (LookupError)
LockupError выступает базовым классом для исключений, которые происходят, когда key или index используются для связывания или последовательность списка/словаря неверна или не существует.
Здесь есть два вида исключений:
- Ошибка индекса (
IndexError); - Ошибка ключа (
KeyError);
Ошибка ключа
Если ключа, к которому нужно получить доступ, не оказывается в словаре, вызывается исключение KeyError.
try:
a = {1:'a', 2:'b', 3:'c'}
print(a[4])
except LookupError:
print("Исключение KeyError.")
else:
print("Успех, нет ошибок!")
Исключение KeyError.
Ошибка индекса
Если пытаться получить доступ к индексу (последовательности) списка, которого не существует в этом списке или находится вне его диапазона, будет вызвана ошибка индекса (IndexError: list index out of range python).
try:
a = ['a', 'b', 'c']
print(a[4])
except LookupError:
print("Исключение IndexError, индекс списка вне диапазона.")
else:
print("Успех, нет ошибок!")
Исключение IndexError, индекс списка вне диапазона.
Ошибка памяти (MemoryError)
Как уже упоминалось, ошибка памяти вызывается, когда операции не хватает памяти для выполнения.
Ошибка имени (NameError)
Ошибка имени возникает, когда локальное или глобальное имя не находится.
В следующем примере переменная ans не определена. Результатом будет ошибка NameError.
try:
print(ans)
except NameError:
print("NameError: переменная 'ans' не определена")
else:
print("Успех, нет ошибок!")
NameError: переменная 'ans' не определена
Ошибка выполнения (Runtime Error)
Ошибка «NotImplementedError»
Ошибка выполнения служит базовым классом для ошибки NotImplemented. Абстрактные методы определенного пользователем класса вызывают это исключение, когда производные методы перезаписывают оригинальный.
class BaseClass(object):
"""Опередляем класс"""
def __init__(self):
super(BaseClass, self).__init__()
def do_something(self):
# функция ничего не делает
raise NotImplementedError(self.__class__.__name__ + '.do_something')
class SubClass(BaseClass):
"""Реализует функцию"""
def do_something(self):
# действительно что-то делает
print(self.__class__.__name__ + ' что-то делает!')
SubClass().do_something()
BaseClass().do_something()
SubClass что-то делает!
---------------------------------------------------------------------------
NotImplementedError Traceback (most recent call last)
in
14
15 SubClass().do_something()
---> 16 BaseClass().do_something()
in do_something(self)
5 def do_something(self):
6 # функция ничего не делает
----> 7 raise NotImplementedError(self.__class__.__name__ + '.do_something')
8
9 class SubClass(BaseClass):
NotImplementedError: BaseClass.do_something
Ошибка типа (TypeError)
Ошибка типа вызывается при попытке объединить два несовместимых операнда или объекта.
В примере ниже целое число пытаются добавить к строке, что приводит к ошибке типа.
try:
a = 5
b = "PythonRu"
c = a + b
except TypeError:
print('Исключение TypeError')
else:
print('Успех, нет ошибок!')
Исключение TypeError
Ошибка значения (ValueError)
Ошибка значения вызывается, когда встроенная операция или функция получают аргумент с корректным типом, но недопустимым значением.
В этом примере встроенная операция float получат аргумент, представляющий собой последовательность символов (значение), что является недопустимым значением для типа: число с плавающей точкой.
try:
print(float('PythonRu'))
except ValueError:
print('ValueError: не удалось преобразовать строку в float: 'PythonRu'')
else:
print('Успех, нет ошибок!')
ValueError: не удалось преобразовать строку в float: 'PythonRu'
Пользовательские исключения в Python
В Python есть много встроенных исключений для использования в программе. Но иногда нужно создавать собственные со своими сообщениями для конкретных целей.
Это можно сделать, создав новый класс, который будет наследовать из класса Exception в Python.
class UnAcceptedValueError(Exception):
def __init__(self, data):
self.data = data
def __str__(self):
return repr(self.data)
Total_Marks = int(input("Введите общее количество баллов: "))
try:
Num_of_Sections = int(input("Введите количество разделов: "))
if(Num_of_Sections < 1):
raise UnAcceptedValueError("Количество секций не может быть меньше 1")
except UnAcceptedValueError as e:
print("Полученная ошибка:", e.data)
Введите общее количество баллов: 10
Введите количество разделов: 0
Полученная ошибка: Количество секций не может быть меньше 1
В предыдущем примере если ввести что-либо меньше 1, будет вызвано исключение. Многие стандартные исключения имеют собственные исключения, которые вызываются при возникновении проблем в работе их функций.
Недостатки обработки исключений в Python
У использования исключений есть свои побочные эффекты, как, например, то, что программы с блоками try-except работают медленнее, а количество кода возрастает.
Дальше пример, где модуль Python timeit используется для проверки времени исполнения 2 разных инструкций. В stmt1 для обработки ZeroDivisionError используется try-except, а в stmt2 — if. Затем они выполняются 10000 раз с переменной a=0. Суть в том, чтобы показать разницу во времени исполнения инструкций. Так, stmt1 с обработкой исключений занимает больше времени чем stmt2, который просто проверяет значение и не делает ничего, если условие не выполнено.
Поэтому стоит ограничить использование обработки исключений в Python и применять его в редких случаях. Например, когда вы не уверены, что будет вводом: целое или число с плавающей точкой, или не уверены, существует ли файл, который нужно открыть.
import timeit
setup="a=0"
stmt1 = '''
try:
b=10/a
except ZeroDivisionError:
pass'''
stmt2 = '''
if a!=0:
b=10/a'''
print("time=",timeit.timeit(stmt1,setup,number=10000))
print("time=",timeit.timeit(stmt2,setup,number=10000))
time= 0.003897680000136461
time= 0.0002797570000439009
Выводы!
Как вы могли увидеть, обработка исключений помогает прервать типичный поток программы с помощью специального механизма, который делает код более отказоустойчивым.
Обработка исключений — один из основных факторов, который делает код готовым к развертыванию. Это простая концепция, построенная всего на 4 блоках: try выискивает исключения, а except их обрабатывает.
Очень важно поупражняться в их использовании, чтобы сделать свой код более отказоустойчивым.


