Up: Testing

Exceptions

slide 01 Hello, and welcome to the second episode of the Software Carpentry lecture on testing. In this episode, we’re going to show you how to handle errors in program using exceptions. Strictly speaking, this isn’t part of testing, but we have to put it somewhere, and since you’re going to want to test how your programs behave when things don’t go as planned, this seems like as good place a place as any.
slide 02 It’s a sad fact, but things sometimes go wrong in programs.
slide 03 Some of these errors have external causes…
slide 04 …like missing or badly-formatted files.
slide 05 Others are internal…
slide 06 …like bugs in code.
slide 07 Either way, there’s no need for panic.
slide 08 It’s actually pretty easy to handle errors in sensible ways. First, though, let’s have a look at how programmers used to do error handling.
slide 09 Back in the Dark Ages, programmers would have functions return some sort of status to indicate whether they had run correctly or not.
slide 10 This led to code like this.
slide 11 The stuff in green is what we really want.
slide 12 The stuff in red is there to check that files were opened and read properly, and to report errors and exit if not.
slide 13 A lot of C and Fortran code is still written this way, but this coding style makes it hard to see the forest for the trees.
slide 14 When we’re reading a program, we want to understand what’s supposed to happen when everything works…
slide 15 …and only then think about what might happen if something goes wrong. When the two are interleaved, both are harder to understand.
slide 16 The net result is that most programmers don’t bother to check the status codes their functions return.
slide 17 Which means that when errors do occur, they’re even harder to track down.
slide 18 Luckily, there’s a better way. Modern languages like Python allow us to use exceptions to handle errors.
slide 19 More specifically, using exceptions allows us to separate the “normal” flow of control from the “exceptional” cases that arise when something goes wrong.
slide 20 This makes both easier to understand.
slide 21 Basically, what exceptions allow us to do is take the code we were just looking at…
slide 22 …and put the “normal” parts in one place…
slide 23 …and all the error-handling parts in another.
slide 24 As a fringe benefit, this often allows us to eliminate redundancy in our error handling.
slide 25 To join the two parts together, we use the keywords try and except. These work together like if and else: the statements under the try are what should happen if everything works, while the statements under except are what the program should do if something goes wrong.
slide 26 You have actually seen exceptions before without knowing it.
slide 27 For example, trying to open a nonexistent file triggers a type of exception called an IOError
slide 28 …while an out-of-bounds index to a list triggers an IndexError. By default, when an exception occurs, Python prints it out and halts our program.
slide 29 We can use try and except to deal with these errors ourselves if we don’t want that to happen.
slide 30 Here, for example, we put our attempt to open a nonexistent file inside a try, and in the except, we print a not-very-helpful error message.
slide 31 Notice that the output is blue, signalling that it was printed normally, rather than red, which is shown for errors.
slide 32 When Python executes this code, it runs the statement inside the try. If that works, it skips over the except block without running it.
slide 33 If an exception occurs inside the try block, though, Python compares the type of the exception to the type specified by the except. If they match, it executes the code in the except block.
slide 34 Note, by the way, that IOError is Python’s way of reporting several kinds of problems related to input and output: not just files that don’t exist, but also things like not having permission to read files, and so on, so we can handle several types of error in one place.
slide 35 We can put as many lines of code in a try block as we want, just as we can put many statements under an if.
slide 36 We can also handle several different kinds of errors afteward.
slide 37 For example, here’s some code to calculate the entropy at each point in a grid.
slide 38 Python tries to run the four statements inside the try as normal. If an error occurs in any of them, Python immediately bails out and tries to find an except whose type matches the type of the error that occurred.
slide 39 If it’s an IOError, Python jumps into the first error handler.
slide 40 If it’s an ArithmeticError, Python jumps into the second handler instead. It will only execute one of these, just as it will only execute one branch of a series of if/elif/else statements.
slide 41 This layout has made the code easier to read, but we’ve lost something important: the message printed out by the IOError branch doesn’t tell us which file caused the problem. We can do better if we capture and hang on to the object that Python creates to record information about the error.
slide 42 In Python version 2.6 and earlier, we do this by putting a variable name after the name of the exception type, separating the two with a comma.
slide 43 If something goes wrong in the try, Python will create an exception object, fill it with information, and assign it to the variable error. (There’s nothing special about this variable name—we can use anything we want.)
slide 44 Exactly what information is recorded depends on what kind of error occurred. Python’s documentation describes the properties of each type of error in detail, but we can always just print the exception object.
slide 45 Python 2.7 and higher allow us to make this a bit more readable using the keyword as. The old style still works, but most new code is written using the new syntax.
slide 46 Now let’s go back and create better error messges.
slide 47 Here’s the modified code.
slide 48 And here are the changes.
slide 49 In the case of an I/O error, we print out the name of the file that caused the problem.
slide 50 And in the case of an arithmetic error, printing out the message embedded in the exception object is what Python would have done anyway.
slide 51 So much for how exceptions work: how should they be used?
slide 52 Some programmers use try and except to give their programs default behaviors. For example, if this code can’t read the grid file that the user has asked for, it creates a default grid instead.
slide 53 Other programmers would explicitly test for the grid file, and use if and else for control flow.
slide 54 It’s mostly a matter of taste, but we prefer the code on the right. As a rule, exceptions should only be used to handle exceptional cases; if the program knows how to fall back to a default grid, that’s not an unexpected event. Using if and else instead of try and except sends different signals to anyone reading our code, even if they do the same thing.
slide 55 Novices often ask another question about exception handling style as well…
slide 56 …but before we address it, there’s something in our example that you might not have noticed.
slide 57 Exceptions can actually be thrown a long way: they don’t have to be handled immediately.
slide 58 Take another look at this code.
slide 59 The four lines in the try block are all function calls.
slide 60 They might catch and handle exceptions themselves, but if an exception occurs in one of them that isn’t handled internally…
slide 61 …Python looks in the calling code for a matching except. If it doesn’t find one there, it looks in that function’s caller, and so on. If we get all the way back to the main program without finding an exception handler, Python’s default behavior is to print an error message like the ones you’ve been seeing all along.
slide 62 This rule is the origin of the saying, “Throw low, catch high.”
slide 63 There are many places in your program where an error might occur.
slide 64 There are only a few, though, where errors can sensibly be handled.
slide 65 For example, a linear algebra library doesn’t know whether it’s being called directly from the Python interpreter, or whether it’s being used as a component in a larger program. In the latter case, the library doesn’t know if the program that’s calling it is being run from the command line or from a GUI.
slide 66 The library therefore shouldn’t try to handle or report errors itself, because it has no way of knowing what the right way to do this is. Instead, it should just raise an exception, and let its caller figure out how best to handle it.
slide 67 Finally, you can raise exceptions yourself if you want to.
slide 68 In fact, you should do this, since it’s the standard way in Python to signal that something has gone wrong.
slide 69 Here, for example, is a function that reads a grid and checks its consistency.
slide 70 The raise statement creates a new exception with a meaningful error message. Since read_grid itself doesn’t contain a try/except block, this exception will always be thrown up and out of the function, to be caught and handled by whoever is calling read_grid.
slide 71 You can define new types of exceptions if you want to.
slide 72 And in fact you should, so that errors in your code can be distinguished from errors in other people’s code.
slide 73 However, this involves classes and objects, so we’ll cover it in the lecture on object-oriented programming.
slide 74

  1. Davi Post
    April 28th, 2011 at 05:24 | #1

    You say “Novices often ask another question about exception handling style as well…”, but defer answering it, and then don’t get back to it in this lesson. That’s confusing to me. Why bother saying this?

  1. No trackbacks yet.