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
andindexError
are exceptions, but there are many more kinds of exceptionsConsider how, like the
print
function, someone had to write the code for converting strings to integers and indexing elements form a listIf 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 integerShould 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
functionAll I need to do is communicate to them that something exceptional happened by
rais
int an exceptionThe function first checks if
b
is0
If
b
is0
, then the exception israise
edIf 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 todayWhen you are using it today, you end up calling
divide(9,0)
, which is problematicOne 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 needCrash?
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 notraise
an exceptionThe code will run normally
except
when the exceptional situation arisesIf 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 withIf it turns out that the code does
raise
an exception, the code within theexcept
block runsIf 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 numberA reasonable way to manage a
ZeroDivisionError
is to use aNaN
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 noZeroDivisionError
, then the division occurs and thequotient
is returnedOn the other hand, if a
ZeroDivisionError
happens, we assignNaN
toquotient
and return itEither 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 precisionGiven 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