Two very common methods for defensive programming are:
Set yourself up for easy testing and debugging
When are you ready to test?
Classes of tests:
Testing approaches:
def is_bigger(x, y):
""" Assumes x and y are ints
Returns True if x is less than y, else False """
What are some natural partitions to check for? I can think of three partitions: one where x is less than y, second where x is equal to y, and third where x is greater than y.
Blackbox testing
def sqrt(x, eps):
""" Assumes x and eps are non-negative floats
Returns res such that x - eps <= res*res <= x+eps"""
| Case | x | eps |
| boundary | 0 | 0.00001 |
| perfect square | 25 | 0.00001 |
| less than 1 | 0.05 | 0.00001 |
| irrational square root | 2 | 0.00001 |
| extremes | 2 | 1.0/2.0**64.0 |
| extremes | 1.0/2.0**64.0 | 1.0/2.0**64.0 |
| extremes | 2.0**64.0 | 1.0/2.0**64.0 |
| extremes | 1/2.0**64.0 | 2.0**64.0 |
| extremes | 2.0**64.0 | 2.0**64.0 |
Glassbox testing
Path-complete testing is not necessarily enough
def abs(x):
""" Assumes x is an int
Returns x if x>=0 and –x otherwise """
if x < -1:
return –x
else:
return x
abs(-1) incorrectly returns -1Debugging
Print statements
Debugging steps
Error messages - easy Consider the following program
test = [1, 2, 3]
test[4] # IndexError
int(test) #TypeError
a #NameError
'3'/4 #TypeError
a = len([1, 2, 3] print(a) #Syntax Error
Logic Errors - Hard
Exceptions and Assertions
test = [1, 7, 4] test[4] # IndexError
int(test) # TypeError
a # NameError
'a'/4 # TypeError
Other examples of exceptions: already seen common datatypes
SyntaxError: Python can't parse programNameError: Local or global name not foundAttributeError: Attribute reference failsTypeError: Operand does not have correct typeValueError: Operand type okay, but value is illegalIOError: IO system reports malfunction (e.g., file not found)Dealing with exceptions
try:
a = int(input("Tell me one number:"))
b = int(input("Tell me another number:"))
print(a/b)
except:
print("Bug in user input.")
except statement.
You can have separate except clauses to deal with a particular type of exception
try:
a = int(input("Tell me one number:"))
b = int(input("Tell me another number:"))
print(a/b)
except ValueError:
print("Could not convert to a number.") # only executes if ValueError comes up
except ZeroDivisionError:
print("Can't divide by zero.") # only executes if ZeroDivisionError comes up
except: # for all other errors
print("Something else went wrong.")
What to do when you encounter an exception?
raise Exception("descriptive string")
Exceptions as control flow
raise keyword can be used to indicate an exception (with its name and
description) at runtime.except block at the very end.raise <exceptionName>(<arguments>)Example:
raise ValueError("something is wrong") #The string description is optional
Example: raising an exception
def get_ratios(L1, L2):
""" Assumes: L1 and L2 are lists of equal length of numbers
Returns: a list containing L1[i]/L2[i] """
ratios = []
for index in range(len(L1)):
try:
ratios.append(L1[index]/L2[index])
except ZeroDivisionError:
ratios.append(float('nan')) #nan = not a number
except:
raise ValueError('get_ratios called with bad arg') #manage flow of program by raising own error
return ratios
get_ratios("") #will raise ValueError. Can enclose this within an except block
Notice that an exception searches for the innermost try/except block. If it does not exist in the
current function, it exits the function (destroying its scope) and going to the caller's scope, caller's caller's scope,
and so on, until a try/except block is found. If no try/except
block is found, the program terminates and the user is shown the details of the exception raised.
Example: We are given a class list for a subject, such that each list entry is a list of two parts:
marks = [ [ ['rahul', 'tendulkar'], [80.0, 70.0, 85.0] ],
[ ['sachin', 'dravid'], [100.0, 80.0, 74.0] ] ]
Create a new class list, with name, marks, and an average
[ [ ['rahul', 'tendulkar'], [80.0, 70.0, 85.0], 78.33333 ], [ [ 'sachin', 'dravid'], [100.0, 80.0, 74.0], 84.66667 ] ]
Example code
def get_stats(class_list):
new_stats = []
for elt in class_list:
new_stats.append([elt[0], elt[1], avg(elt[1])])
return new_stats
def avg(marklist):
return sum(marklist)/len(marklist)
We will get an error on the following input:
marks = [ [ ['rahul', 'tendulkar'], [80.0, 70.0, 85.0] ],
[ [ 'sachin', 'dravid'], [100.0, 80.0, 74.0] ],
[ ['test'], [] ] ]
Get ZeroDivisionError: float division by zero because try to divide by len(marklist) which
is 0 for test.
Multiple choices for handling these errors using except:
None value.
def avg(marklist):
try:
return sum(marklist)/len(marklist)
except:
print('warning: no marks data')
This results in the following output:
warning: no marks data
marks = [ [ ['rahul', 'tendulkar'], [80.0, 70.0, 85.0], 78.33333 ],
[ [ 'sachin', 'dravid'], [100.0, 80.0, 74.0], 84.66667 ],
[ ['test'], [], None ] ]
Notice that the None value is output because avg did not return anything in the except block, which is equivalent to returning the None value.
0.0.
def avg(marklist):
try:
return sum(marklist)/len(marklist)
except:
print('warning: no marks data')
return 0.0
This results in the following output:
warning: no marks data
marks = [ [ ['rahul', 'tendulkar'], [80.0, 70.0, 85.0], 78.33333 ],
[ [ 'sachin', 'dravid'], [100.0, 80.0, 74.0], 84.66667 ],
[ ['test'], [], 0.0] ]
def avg(marklist):
try:
return sum(marklist)/len(marklist)
except:
print('warning: no marks data')
raise ValueError("marklist found empty")
This results in the following output:
warning: no marks data Traceback: ... .... ValueError: "marklist found empty"
Assertions
assert statement to raise an AssertionError exception if assumptions not met.def avg(marklist): assert len(marklist) != 0, 'no grades data' return sum(marklist)/len(marklist)
AssertionError if it is given an empty marklistAssertions as defensive programming
z0_power_i == z0**i at the beginning the body of a for-loop.Where to use assertions?
n=len(L), or z0_power_i == z0**i at certain program points.Example:
cube = int(input("Enter a number: ")
for guess in range(abs(cube)+1):
assert(guess**3 <= abs(cube))
if guess**3 >= abs(cube):
break
assert(guess**3 < abs(cube))
if guess**3 != abs(cube):
print(cube, 'is not a perfect cube')
else:
if cube < 0:
guess = -guess
print('Cube root of ' + str(cube) + ' is ' + str(guess))
Another example:
cube = int(input("Enter a number: ")
epsilon = 0.01
low = 0
high = cube
guess = (low+high)/2.0
num_guesses = 0
while abs(guess**3 - cube) >= epsilon:
assert(low <= guess)
assert(guess <= high)
if guess**3 < cube:
low = guess
else:
high = guess
guess = (high + low)/2.0
num_guesses += 1
print 'num_guesses =', num_guesses
print(guess, 'is close to the cube root of', cube)
Example on longest common subsequence (LCS):
def isCommonSubsequence(X, Y, R):
xi = 0
yi = 0
for c in R:
while X[xi] != c:
xi+=1
if xi == len(X):
return False
while Y[yi] != c:
yi+=1
if yi == len(Y):
return False
return True
def lcs(X, Y):
ret = ""
if len(X) == 0 or len(Y) == 0:
ret = ""
elif X[-1] == Y[-1]:
lcsXY = lcs(X[0:-1], Y[0:-1])
ret = lcsXY + X[-1]
else:
lcsY = lcs(X[0:-1], Y)
lcsX = lcs(X, Y[0:-1])
if len(lcsY) < len(lcsX):
ret = lcsX
else:
ret = lcsY
assert(isCommonSubsequence(X, Y, ret))
return ret
Example on placing N queens in an NxN chessboard
global N
N = 4
def printSolution(board):
for i in range(N):
for j in range(N):
if board[i][j] == 1:
print("Q",end=" ")
else:
print(".",end=" ")
print()
def isSafe(board, row, col):
# Check this row on left side
for i in range(col):
if board[row][i] == 1:
return False
# Check upper diagonal on left side
for i, j in zip(range(row, -1, -1),
range(col, -1, -1)):
if board[i][j] == 1:
return False
# Check lower diagonal on left side
for i, j in zip(range(row, N, 1),
range(col, -1, -1)):
if board[i][j] == 1:
return False
return True
def solveNQUtil(board, col):
# Base case: If all queens are placed
# then return true
if col >= N:
return True
# Consider this column and try placing
# this queen in all rows one by one
for i in range(N):
if isSafe(board, i, col):
# Place the queen at board[i][col]
board[i][col] = 1
# Recurse to place rest of the queens
if solveNQUtil(board, col + 1) == True:
return True
board[i][col] = 0
return False
def solveNQ():
board = [[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]]
if solveNQUtil(board, 0) == False:
print("Solution does not exist")
return False
for i in range(0, N, 1):
for j in range(0, N, 1):
assert (!board[i][j] || isSafe(board, i, j))
printSolution(board)
return True