Python ‘assert’: Invariants in Code

python assertIn Python, as developers find bugs, and issues in their design, they enforce their code with advanced techniques that we will cover in this blog. Any unexpected situations in the code will trigger the assertions and lead to aborting the programs, and identifying the bugs for future improvement. How do software developers build robust programs? It is with assertions and unit tests, that developers improve their codes.

On a simpler level, how do we change software, code – programs, and algorithms – without losing the original functionality? In this blog post we will learn about writing a simple Python program and updating it as requirements change, all the time using unit tests to capture this behavior. We will learn how web developers enforce their designs, and gain confidence.

Meet the Assert Statement

If you a beginning Python user, you may benefit from using the Python interpreter interactively, via the IDLE environment in Windows, or the shell in Linux. Start by typing in the shell,

$ python

and you should see the following prompt inviting you to explore the language.

user@host:/usr/username/$ python
Python 2.7.3 (default, Sep 26 2013, 20:08:41) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 

Follow along by typing into the interpreter the lines of code, and the outputs are highlighted in red, while the code is in black,

>>> assert True
>>> assert 1 + 1
>>> assert 1 + 1 > 0
>>> assert 1 + 1 – 4

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

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

Your empirical experiments have shown you something about assert statement; i.e. on True evaluation of expression assert is quiet, on false evaluation of the statement it raises the AssertionError exception.

Syntax of Assert Statement

The assert statement is a keyword in Python language. You can display a list of keywords by typing in the interpreter,

>>> import keyword
>>> print keyword.kwlist
['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']

The syntax for assert statement looks like,

assert <expression>

where the assert statement evaluates the expression and raises an AssertionError exception when the said expression has a False value in Python. If the expression evaluates to None, False, 0, or [] then assert triggers the AssertionError.

For example,

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

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

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

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

Program Design

Programmers use the assert statement to raise errors to catch unexpected behavior of their code. For example if you wrote a program to calculate the volume of a solid, or area of a polygon, you can include an assertion that the volume, or area, respectively should not be zero; i.e. assert area > 0

Examples – Using Assertions in Factorial Function

Factorial of a number, N, is defined to be the product of numbers from 1 to N; i.e. N! = 1*2*3* … N. Clearly a straightforward way to calculate factorial is using a for-loop, but we prefer to use a recursive definition. This recursive form is equivalent, and accurate, mathematical definition of factorial function is using the notation; i.e. N! = (N-1)!*N. Here we can use the assert statement to ensure the result of factorial is always positive.

Clearly a way of writing this as a program in iterative and recursive ways would be,

# recursive way of writing factorial
def fact( n ):
    if ( n == 0 ):
        return 1.0
    return fact( n - 1)*n;

if __name__ == "__main__":
    for i in range(0,10+1):
	  fact_val = fact(i);
	  assert( fact_val > 0 ) #factorial is always non-zero
        print("%d! = %g"%(i,fact_val))

Running the Program

You can download Python package for your platform from the source website, Python.org, and run the tests and programs as, $ python factorial_v1.py, is the command to interpret the code and then run the program

0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3.6288e+06

Bugs and Fixes

There are various bugs in the function in the program few of which are,

  1. Function does blows up with negative inputs. You need to have include guards
  2. Function does not work with complex inputs
  3. Numerical overflow for large input n

The following listing fixes the bugs

# recursive way of writing factorial
def fact( n ):
    if ( isinstance( n, complex) ):
        raise Exception(“Cannot calculate factorial of complex function”)
    if ( n == 0 ):
        return 1.0
    elif ( n < 0 ):
        raise Exception("Cannot calculate factorial for negative numbers")
    assert( n > 0 ) #since all other cases are treated elsewhere
    return fact( n - 1)*n;

if __name__ == "__main__":
    for i in range(0,10+1):
        print("%d! = %g"%(i,fact(i)))
    print("%d! = %g"%(-5+4j,fact( -5 + 4j ))) #raises exception for complex
    print("%d! = %g"%(-5,fact( -5 ))) #raises exception

Adding a Unit Test

Now let us focus on writing the test cases which exercise the function in various modes, and lock down the behavior.

Python has a powerful unit test framework called the unittest module, which we will use to write unit tests to ensure our solution against possible regression.

Each test point lives in a function named as ‘test_’ and it exercises the various cases of the ‘fact’ and compares the results against the inbuilt math function ‘math.factorial’. Usually the comparison of function output with known results is done using the unit-test API methods in the next section.

The code listing for ‘factorial_test.py’ follows,

import unittest
import math
import factorial_v1
from test import test_support

class FactorialTest(unittest.TestCase):
    def setUp(self):
        print("setup")

    def tearDown(self):
        print("cleanup")

    def test_positives(self):
        for x in range(0,10+1):
            act = math.factorial( x )
            val = factorial_v1.fact( x )
            print("%d! = %g == %g"%(x,val,act))
            self.assertAlmostEqual( act, val, 1e-1 )

    def test_negative(self):
        passed = False
        try:
            factorial_v1.fact( -3 )
        except Exception as e:
            passed = True and (e.message.find("Cannot calculate")>= 0 )
        self.assertTrue( passed )

        ## alternate way
        with self.assertRaises( Exception ) as cm:
            factorial_v1.fact(-3)

if __name__ == "__main__":
    test_support.run_unittest(FactorialTest)

and run it on the Python interpreter to see a message which shows the 2 unittest have passed, and happy ‘OK’ message is printed on screen.

Summary

Software developers use assertions to enforce invariants in the design of programs, and testing those programs against regression. The python assert statement is one of key tools to enforce invariants in code, and in testing enabling users and developers of programs to progress without regression. Continue to stay on top and learn more about regression testing and software design.