18. Exceptions

  • You have all seen various exceptions in Python

int('hello')
ValueError: invalid literal for int() with base 10: 'hello'
  • In the above example, when we tried to convert the string "hello" to an integer, Python raised an exception

a = ['a', 'e', 'i', 'o', 'u']
print(a[11])
IndexError: list index out of range
  • In the above example, when we tried to access the 11th thing in the list containing only 5 things, Python raised an exception

  • Both ValueError and indexError are exceptions, but there are many more kinds of exceptions

  • Consider how, like the print function, someone had to write the code for converting strings to integers and indexing elements form a list

  • If I am the one writing the code for converting strings to integers, what should I make my code do if someone asks my code to convert the string "hello" to an integer?

    • Obviously there is no single obvious and natural way to convert the string "hello" to an integer

    • Should my code simply ignore the request and carry on like nothing ever happened?

    • Should my code crash the whole program?

    • Maybe some user entered some input wrong?

    • Maybe the issue is with some text file that was read?

  • The trouble is, if I am writing the code for converting strings to integers, I cannot possibly know what you — the individual trying to use my code at some point in the future — want to do in these exceptional situations

  • What I can do however is raise an exception, which then communicates to the programmer using my code that they have to specify what they want to do when the exceptional situation arises

  • Consider the example below on dividing by zero

1def divide(a,b):
2    if b == 0:
3        raise ZeroDivisionError("Wait, that's illegal")
4    else:
5        return a/b
  • Obviously we’re going to have an issue if we try to divide a number by zero

  • But what should happen if someone tries to?

  • That’s entirely up to the programmer making use of the divide function

  • All I need to do is communicate to them that something exceptional happened by raisint an exception

    • The function first checks if b is 0

    • If b is 0, then the exception is raiseed

    • If it is not, then the function carries on as it should

18.1. Catching Exceptions

  • Pretend the above function divide was written in 1991 and you want to use it today

  • When you are using it today, you end up calling divide(9,0), which is problematic

    • One cannot divide 9 by 0

    • What should the original programmer in 1991 do about it?

    • Well… not much considering you are the one trying to use the function today

    • How could the programmer know what to do to handle your specific situation today?

  • What the original programmer can do, however, is to add some special code that says something exceptional happened that lets you know that something is off

  • This then allows you, the individual trying to use divide today, to deal with the situation the way you need

    • Crash?

    • Read input again?

    • Carry on as if nothing happened?

  • If you are using a function that may result in an exception, you can write your code such that

    • You try to run the code that may or may not raise an exception

    • The code will run normally except when the exceptional situation arises

    • If the exceptional situation arises, special instructions will be specified

  • Below is a generalized idea of how one can do this

def my_code():
    try:
        might
        cause
        exception
    except SomeError:
        will
        handle
        exception
    runs
    regardless
  • In the above example, the code in the try would be something that may cause an exception we want to deal with

  • If it turns out that the code does raise an exception, the code within the except block runs

  • If no exception arises, then the except block is skipped

18.1.1. Example 1

  • There exists a special value for floating point numbers in Python called NaN, which means not a number

  • A reasonable way to manage a ZeroDivisionError is to use a NaN value

1def not_a_number_example(a: float, b: float) -> float:
2    try:
3        quotient = divide(a, b)
4    except ZeroDivisionError:
5        quotient = float("NaN")
6    return quotient
  • If divide is called and there is no ZeroDivisionError, then the division occurs and the quotient is returned

  • On the other hand, if a ZeroDivisionError happens, we assign NaN to quotient and return it

  • Either way, the quotient is returned

18.1.2. Example 2

  • Consider a program requiring a user to input some values

  • If this is the case, it may be ideal to have the program ask for input again if the input was inadmissible

 1def continue_asking_for_input() -> float:
 2    while True:
 3        data = input("Provide operands for division: ").split()
 4        a = float(data[0])
 5        b = float(data[1])
 6        try:
 7            quotient = divide(a, b)
 8            break
 9        except ZeroDivisionError:
10            print("Cannot Divide By Zero --- Try Again")
11
12    return quotient

18.1.3. Example 3

  • It may be the case that if the function causes an exception, our program should stop running immediately

  • If this is the case, we can make use of the exit() function to halt the program

1def stop_running_immediately(a,b) -> float:
2    try:
3        quotient = divide(a,b)
4    except ZeroDivisionError:
5        exit()      # Immediately stop!
6    return quotient
  • Alternatively, a simpler and better implementation would be to just let the exception propagate up and have the program eventually stop as a result

1def stop_running_immediately(a,b) -> float:
2    quotient = divide(a,b)
3    return quotient
  • In the above example, if divide causes an exception, the exception would keep being handed to the calling function until it is dealt with or ultimately crashes the program

18.1.4. Example 4

  • Sometimes the exception may be inconsequential for the program’s functionality

    • Perhaps your program is rapidly reading input from some sensors for calculations and a ZeroDivisionError is likely caused by sensor precision

    • Given this, it may be the case that periodic ZeroDivisionError are meaningless, so they can be ignored

1    try:
2        quotient = divide(a,b)
3    except ZeroDivisionError:
4        pass

18.2. Why Care?

  • Which of the above examples is the correct one?

  • The trouble is, that depends on your situation

  • The point is, how can the programmer in 1991 know what you want to do with your situation today?

  • Ultimately, exceptions are useful because

    • They allow programmers to pass info around and communicate through time

    • They allow us to deal with exceptional situations effectively

    • They provide a nice logical division between normal code and exceptional code

18.3. For Next Class