Error Handling
7.1 Introduction to Error Handling
7.1.1 Try and Except
try delimits the code that we want to execute, while the except delimits the code that will be executed if there is an error in the code under the try block. Errors detected during execution are called exceptions. Let’s look at the general outline:
try:
code block 1
# ...some error prone code...
except:
code block 2
# ...do something with the error...
[else:
This code will first try to execute the code in block 1. If the code is executed without problems, the flow of execution continues through the code in block 3 and finally through block 4. In case the code in block 1 produces an error (or raises an exception according to the jargon), the code in block 2 will be executed and then the code in block 4. The idea behind this mechanism is to put the block of code that we suspect may produce an error (block 1), inside
the try clause. The code that does should be called in case of a problem is placed in the except block. This code (code block 2) deals with the exception, or in another words, it handles the exception. Error messages are what the user gets when exceptions are not handled:
>>> 0/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Optionally, it is possible to add the statement else, which will be executed only if the code inside try (code block 1) executes successfully. Note that the code below else can be placed in the try block because it would have the same effect (it would execute if there are no errors). The actual difference is conceptual, the block inside try should contain only the code that is suspected to raise an exception, while we would have to leave for the block below else the instructions that should be executed when the instructions below try are executed without error. Note that the code inside finally is always executed.
This is an oversimplified example:
try:
print 0/0 except:
print("Houston, we have a problem...") The result is:
Houston, we have a problem...
The first thing that we take note of is that neither else nor finally is included as they are optional statements. In this case, the statement print 0/0 raises an exception. This exception is “caught” by the code that follows except. This way we can securely test a block of code as any error that occurs will redirect the program flow in a predictable way.
In this code exception handling is applied to code listing 7.2:
Listing 7.3: Similar to 7.2 but with exception handling (py3.us/23). Python 2.x only.
1 import os 2 while True:
3 try:
4 iname = raw_input("Enter input filename: ") 5 oname = raw_input("Enter output filename: ")
6 fh = open(iname)
7 line = fh.readline()
8 fh.close()
15 print "Can’t write to outfile."
16 elif errno==2:
17 print "File not exist"
18 except ValueError, strerror:
19 if "substring not found" in strerror.message:
20 print "There is no tab"
21 elif "invalid literal for int" in strerror.message:
22 print "The value can’t be converted to int"
23 else:
24 print "Thank you!. Everything went OK."
25 break
At first look it is noticieable that this code is easier to follow than the previous version (7.2). At least the code logic is separated from the error handling. We can note that from line 4 to 12, the same code is repeated as in the original listing (7.1). It is from line 13 where the exception handling begins. According to the type of exception, it is the code that will be executed below. We will see how to distinguish between the different types of exceptions later.
Listing 7.3 is an introductory example of how to apply exception handling to the listing 7.1, and not a definitive guide of how to handle exceptions.
For example if the integer conversion of line 11 fails, an exception of type ValueError will be raised, a message will be printed and the program flow will return to line 3 (because it is under a while TRUE), without releasing the resources used (the filehandle fw). One way to solve this problem is to copy the statement where the resource is closed (line 12) to the block where the corresponding exception is managed (after line 22). This way we ensure to release the resource.
Listing 7.4: Another version of 7.3 code (py3.us/24) 1 import os
2 while True:
3 try:
4 iname = raw_input("Enter input filename: ") 5 oname = raw_input("Enter output filename: ")
6 fh = open(iname)
7 line = fh.readline()
8 fh.close()
15 print("Can’t write to outfile.")
16 elif errno==2:
17 print("File not exist") 18 except ValueError, strerror:
19 if "substring not found" in strerror.message:
20 print("There is no tab")
21 elif "invalid literal for int" in strerror.message:
22 print("The value can’t be converted to int")
23 fw.close()
24 else:
25 print("Thank you!. Everything went OK.")
26 break
Even if this code does its job, it is not a good idea to repeat the same statement (fw.close()) in two places (line 12 and 23). If we have a block of code that always needs to be executed whether or not an error occurs we can include it in finally, which is where the code that is executed independently of what happens with the code in try. The problem with including fw.close() under finally is that there may be an exception before opening fh (for example in the integer conversion, line 10 of the listing 7.4) and we are going to try to close a file that was never opened, which will cause another error by itself.
This error in turn, can be predicted, for which we can use the exception mechanism and include a try/except clause within finally:
Listing 7.5: Code with nested exceptions 1 import os
2 while True:
3 try:
4 iname = raw_input("Enter input filename: ") 5 oname = raw_input("Enter output filename: ")
6 fh = open(iname)
13 if errno==13:
14 print("Can’t write to outfile.")
15 elif errno==2:
16 print("File not exist") 17 except ValueError, strerror:
18 if "substring not found" in strerror.message:
19 print("There is no tab")
20 elif "invalid literal for int" in strerror.message:
21 print("The value can’t be converted to int")
22 else:
23 print("Thank you!. Everything went OK.")
24 break
The code from the listing 7.5 was made to illustrate the use of a nested try, but it is not the best way to solve the problem. We may avoid causing an exception while the filehandle is open by making the integer conversion before opening the file. Another change to consider is to remove the raw input statements in the try block, because it is convenient to include only the statements that are expected to cause exceptions.
Listing 7.6: Similar to code 7.4 without nested exceptions 1 import os, errno
2 while True:
3 iname = raw_input("Enter input filename: ") 4 oname = raw_input("Enter output filename: ")
5 try:
17 if "substring not found" in strerror.message:
18 print("There is no tab")
19 elif "invalid literal for int" in strerror.message:
20 print("The value can’t be converted to int")
21 else:
22 fw.close()
23 fh.close()
24 break
We’ve seen in general terms how the try/except, clause works and now we can go a little deeper to discuss the types of exceptions: