TYPES OF EXCEPTIONS

Common error types:

  • SyntaxError: Python can’t parse program
  • NameError: local or global name not found
  • AttributeError: attribute reference fails
  • TypeError: operand doesn’t have correct type
  • ValueError: operand type okay, but value is illegal
  • IOError: IO system reports malfunction (e.g. file not found)

WHAT TO DO WITH EXCEPTIONS?

fail silently:

  • substitute default values or just continue
  • bad idea! user gets no warning

return an “error” value

  • what value to choose?
  • complicates code having to check for a special value

stop execution, signal error condition

  • in Python: raise an exceptionraise Exception("descriptive string")

DEALING WITH EXCEPTIONS

Python code can provide handlers for exceptions

In [1]:
try:
    a = int(input("Tell me one number:"))
    b = int(input("Tell me another number:"))
    print(a/b)
    print ("Okay")
except:
    print("Bug in user input.")
print("Outside")
Tell me one number:1
Tell me another number:2
0.5
Okay
Outside
In [2]:
try:
    a = int(input("Tell me one number:"))
    b = int(input("Tell me another number:"))
    print(a/b)
    print ("Okay")
except:
    print("Bug in user input.")
print("Outside")
Tell me one number:4
Tell me another number:0
Bug in user input.
Outside
  • exceptions raisedby any statement in body of try are handled by the excep tstatement and execution continues after the body of the except statement.

HANDLING SPECIFIC EXCEPTIONS

Have separate exceptclauses to deal with a particular type of exception

In [8]:
try:
    a = int(input("Tell me one number: "))
    b = int(input("Tell me another number: "))
    print("a/b = ", a/b)
    print("a+b= ", a+b)
except ValueError:
    print("Could not convert to a number.")
except ZeroDivisionError:
    print("Can't divide by zero")
except:
    print("Something went very wrong.")
Tell me one number: 1
Tell me another number: 0
Can't divide by zero
In [9]:
try:
    a = int(input("Tell me one number: "))
    b = int(input("Tell me another number: "))
    print("a/b = ", a/b)
    print("a+b= ", a+b)
except ValueError:
    print("Could not convert to a number.")
except ZeroDivisionError:
    print("Can't divide by zero")
except:
    print("Something went very wrong.")
Tell me one number: 4
Tell me another number: a
Could not convert to a number.

OTHER EXCEPTIONS

else:

  • body of this is executed when execution of associated try body completes with no exceptions
  • Ex: print("all the code ran sucessfully")

finally:

  • body of this is always executed after try, elseand exceptclauses, even if they raised another error or executed a break, continueor return
  • useful for clean-up code that should be run no matter what else happened (e.g. close a file)

CONTROL INPUT using EXCEPTIONS

In [16]:
data = []
file_name= input("Provide a name of a file of data ")
try:
    fh = open(file_name, 'r')
except IOError:
    print('cannot open', file_name)
else:
    for new in fh:
        if new != '\n':
            addIt= new[:-1].split(',') #remove trailing \n
            data.append(addIt)
    fh.close() # close file even if fail
finally:
    print("End of the code") # always exicuted 
    
Provide a name of a file of data hello.py
cannot open hello.py
End of the code
In [19]:
#seperate grades and name

L = [['John','Don','56'],['Von','Neouman'],['Mr','Godfather','Corlinone','99']]
gradesData= []
for student in L:
    print(student)
    try:
        name = student[0:-1]
        grades = int(student[-1])
        gradesData.append([name, [grades]])
    except ValueError:                       # if ValueError i.e could not convert string into int in line 9
        gradesData.append([student[:], []])
print(gradesData)
['John', 'Don', '56']
['Von', 'Neouman']
['Mr', 'Godfather', 'Corlinone', '99']
[[['John', 'Don'], [56]], [['Von', 'Neouman'], []], [['Mr', 'Godfather', 'Corlinone'], [99]]]

Exceptions can be raised when needed not just handeled.

example

  • raise ValueError("something is wrong")
In [23]:
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]/float(L2[index]))
        except ZeroDivisionError:
            ratios.append(float('NaN')) #NaN= Not a Number
        except:
            raise ValueError('get_ratioscalled with bad arg') # error that we want to be raised 
    return ratios
L1 = [1,2,3]
L2 = [0,5,8]
get_ratios(L1, L2)
Out[23]:
[nan, 0.4, 0.375]
In [24]:
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]/float(L2[index]))
        except ZeroDivisionError:
            ratios.append(float('NaN')) #NaN= Not a Number
        except:
            raise ValueError('get_ratioscalled with bad arg') # error that we want to be raised 
    return ratios
L1 = [1,2,3,4]
L2 = [0,5,8]
get_ratios(L1, L2)
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-24-d78512caa305> in get_ratios(L1, L2)
      6         try:
----> 7             ratios.append(L1[index]/float(L2[index]))
      8         except ZeroDivisionError:

IndexError: list index out of range

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
<ipython-input-24-d78512caa305> in <module>()
     13 L1 = [1,2,3,4]
     14 L2 = [0,5,8]
---> 15 get_ratios(L1, L2)

<ipython-input-24-d78512caa305> in get_ratios(L1, L2)
      9             ratios.append(float('NaN')) #NaN= Not a Number
     10         except:
---> 11             raise ValueError('get_ratioscalled with bad arg')
     12     return ratios
     13 L1 = [1,2,3,4]

ValueError: get_ratioscalled with bad arg

Assertions

Makes sure the contions required are meet before even computation.

  • assertions don’t allow a programmer to control response to unexpected conditions
  • ensure that execution halts whenever an expected condition is not met
  • typically used to check inputs to functions procedures, but can be used anywhere
  • can be used to check outputs of a function to avoid propagating bad values
  • can make it easier to locate a source of a bug

WHERE TO USE ASSERTIONS?

  • goal is to spot bugs as soon as introduced and make clear where they happened
  • use as a supplement to testing
  • raise exceptions if users supplies bad data input
  • use assertions to
    • check types of arguments or values
    • check that invariants on data structures are met
    • check constraints on return values
    • check for violations of constraints on procedure (e.g. no duplicates in a list)
In [33]:
#example
def normalize(numbers):
    max_number = max(numbers)
    assert(max_number != 0), "Cannot divide by 0" #making sure max is not 0
    for i in range(len(numbers)):
        numbers[i]  /= float(max_number)
        assert(0.0 <= numbers[i] <= 1.0), "output not between 0 and 1"
    return numbers  
numbers = [1,2,3]
normalize(numbers)
Out[33]:
[0.3333333333333333, 0.6666666666666666, 1.0]
In [34]:
#example
def normalize(numbers):
    max_number = max(numbers)
    assert(max_number != 0), "Cannot divide by 0"
    for i in range(len(numbers)):
        numbers[i]  /= float(max_number)
        assert(0.0 <= numbers[i] <= 1.0), "output not between 0 and 1"
    return numbers  
numbers = [0,0,0] # should throw an error 
normalize(numbers)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-34-98f6191cc212> in <module>()
      8     return numbers
      9 numbers = [0,0,0]
---> 10 normalize(numbers)

<ipython-input-34-98f6191cc212> in normalize(numbers)
      2 def normalize(numbers):
      3     max_number = max(numbers)
----> 4     assert(max_number != 0), "Cannot divide by 0"
      5     for i in range(len(numbers)):
      6         numbers[i]  /= float(max_number)

AssertionError: Cannot divide by 0

In [47]:
HAND_SIZE = 7

SCRABBLE_LETTER_VALUES = {
    'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1, 'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10
}

def getWordScore(word, n):
    """
    Returns the score for a word. Assumes the word is a valid word.

    The score for a word is the sum of the points for letters in the
    word, multiplied by the length of the word, PLUS 50 points if all n
    letters are used on the first turn.

    Letters are scored as in Scrabble; A is worth 1, B is worth 3, C is
    worth 3, D is worth 2, E is worth 1, and so on (see SCRABBLE_LETTER_VALUES)

    word: string (lowercase letters)
    n: integer (HAND_SIZE; i.e., hand size required for additional points)
    returns: int >= 0
    """
    score = 0
    for i in word:
        score += SCRABBLE_LETTER_VALUES[i]
    score *= len(word)
    if len(word) == n:
        score += 50
    
    return score

getWordScore("hello", 5)
Out[47]:
90
In [49]:
def updateHand(hand, word):
    """
    Assumes that 'hand' has all the letters in word.
    In other words, this assumes that however many times
    a letter appears in 'word', 'hand' has at least as
    many of that letter in it.

    Updates the hand: uses up the letters in the given word
    and returns the new hand, without those letters in it.

    Has no side effects: does not modify hand.

    word: string
    hand: dictionary (string -> int)
    returns: dictionary (string -> int)
    """
    update = hand.copy()
    for x in word:
        update[x] = update.get(x,0) - 1
    return update

word = 'quail'
hand = {'a':1, 'q':1, 'l':2, 'm':1, 'u':1, 'i':1}
updateHand(hand, 'quail')
Out[49]:
{'a': 0, 'i': 0, 'l': 1, 'm': 1, 'q': 0, 'u': 0}
In [57]:
def isValidWord(word, hand, wordList):
    """
    Returns True if word is in the wordList and is entirely
    composed of letters in the hand. Otherwise, returns False.

    Does not mutate hand or wordList.
   
    word: string
    hand: dictionary (string -> int)
    wordList: list of lowercase strings
    """
    # TO DO ... <-- Remove this comment when you code this function
    flag = 0 
    D = hand.copy()
    for x in word:
        if D.get(x,0) != 0:
            D[x] = D.get(x,0) - 1
        else:
            flag = 1
    if flag == 0:
        if word in wordList:
            return True
    
    return False

word = 'quail'
hand = {'a':1, 'q':1, 'l':2, 'm':1, 'u':1, 'i':1}
wordList = ['quail','hello']
isValidWord(word, hand, wordList)
Out[57]:
True
In [60]:
hand = {'a':1, 'q':1, 'l':2, 'm':1, 'u':1, 'i':0}

handlen = 0
for i in hand:
    handlen += hand[i] 
handlen
Out[60]:
6
In [63]:
input("Print x: ")
Print x: x
Out[63]:
'x'
In [64]:
def displayHand(hand):
    """
    Displays the letters currently in the hand.

    For example:
    >>> displayHand({'a':1, 'x':2, 'l':3, 'e':1})
    Should print out something like:
       a x x l l l e
    The order of the letters is unimportant.

    hand: dictionary (string -> int)
    """
    for letter in hand.keys():
        for j in range(hand[letter]):
             print(letter,end=" ")       # print all on the same line
    print()  

hand = {'a':1, 'q':1, 'l':2, 'm':1, 'u':1, 'i':0}

displayHand(hand)
a q l l m u 
In [78]:
print("Current Hand: ",end='')
displayHand(hand)
Current Hand: a q l l m u 
In [71]:
str(displayHand(hand))
a q l l m u 
Out[71]:
'None'
In [79]:
letter = input("Enter n to deal a new hand, r to replay the last hand, or e to end game: ")
Enter n to deal a new hand, r to replay the last hand, or e to end game: r
In [80]:
letter 
Out[80]:
'r'
In [81]:
print('Hello', end = " ")
print('world', end="?")
print('!')
Hello world?!

Reference

  • edX course offered by MIT
  • 6.00.1x Introduction to Computer Science and Programming Using Python