Python 101: Exception Handling

Python provides robust exception handing baked right into the language. Exception handing is something every programmer will need to learn. It allows the programmer to continue their program or gracefully terminate the application after an exception has occurred. Python uses a try/except/finally convention. We’ll spend some time learning about standard exceptions, how to create a custom exception and how to get the exception information in case we need it for debugging.

Basic Exception Handling

To begin, it must be said that bare exceptions are usually NOT recommended! You will see them used though and I’ve used them myself from time to time. A bare except looks like this:

try:
    print monkey
except:
    print "This is an error message!"

As you can see, this will catch ALL exceptions, but you don’t really know anything about the exception which makes handling it pretty tricky. Because you don’t know what exception was caught, you also can’t print out a very relevant message to the user. On the other hand, there are times when I think it’s just plain easier to use a bare except. One of my readers contacted me about this and said that database interactions are a good example. I’ve noticed that I seem to get a wide array of exceptions when dealing with databases too, so I would agree. I think when you reach more than 3 or 4 exception handlers in a row, it’s better to just use a bare except and if you need the traceback, there are ways to get it (see the last section).

Let’s take a look at a few normal examples of exception handling.

   
try:
    import spam
except ImportError:
    print "Spam for eating, not importing!"

try:
    print python
except NameError, e:
    print e

try:
    1 / 0
except ZeroDivisionError:
    print "You can't divide by zero!"

The first example catches an ImportError only which happens when you import something that Python cannot find. You’ll see this if you try to import SQLAlchemy without actually having it installed or when you mis-spell a module or package name. One good use for catching ImportErrors is when you want to import an alternate module. Take the md5 module. It was deprecated in Python 2.5 so if you are writing code that supports 2.5-3.x, then you may want to try importing the md5 module and then falling back to the newer (and recommended) hashlib module when the import fails.

The next exception is NameError. You will get this when a variable hasn’t been defined yet. You’ll also notice that we added a comma “e” to the exception part. This lets us not only catch the error, but print out the informational part of it too, which in this case would be: name ‘python’ is not defined.

The third and last exception is an example of how to catch a Zero Division exception. Yes, users still try to divide by zero no matter how often you tell them not to. All of these exceptions that we have looked at our subclasses of Exception, so if you were to write an except Exception clause, you would essentially be writing a bare except that catches all exceptions.

Now, what would you do if you wanted to catch multiple errors but not all of them? Or do something no matter what happened? Let’s find out!

try:
    5 + "python"
except TypeError:
    print "You can't add strings and integers!"
except WindowsError:
    print "A Windows error has occurred. Good luck figuring it out!"
finally:
    print "The finally section ALWAYS runs!"

In the code above, you’ll see that we have two except statements under one try. You can add as many excepts as you want or need to and do something different for each one. In Python 2.5 and newer, you can actually string the exceptions together like this: except TypeError, WindowsError: or except (TypeError, WindowsError). You will also notice that we have an optional finally clause that will run whether or not there’s an exception. It is good for cleaning up actions, like closing files or database connections. You can also do a try/except/else/finally or try/except/else, but the else is kind of confusing in practice and to be honest, I’ve never seen it used. Basically the else clause is only executed when the except does NOT get executed. It can be handy if you don’t want to put a bunch of code in the try portion that might raise other errors. See the documentation for more information.

Getting the Entire Stack Traceback

What if you want to get the entire traceback of the exception? Python has a module for that. It’s called, logically enough, traceback. Here’s a quick and dirty little example:

import traceback

try:
    with open("C:/path/to/py.log") as f:
        for line in f:
            print line
except IOError, exception:
    print exception
    print 
    print traceback.print_exc()

The code above will print out the exception text, print a blank line and then print the whole traceback using the traceback module’s print_exc method. There are a bunch of other methods in the traceback module that allow you to format the output or get various portions of the stack trace rather than the full one. You should check out the documentation for more details and examples though.

There’s another way to get the whole traceback without using the traceback module, at least not directly. You can instead use Python’s logging module. Here’s a simple example:

import logging

logging.basicConfig(filename="sample.log", level=logging.INFO)
log = logging.getLogger("ex")
 
try:
    raise RuntimeError
except RuntimeError, err:
    log.exception("RuntimeError!")

This will create a log file in the same directory that the script is run in with the following contents:

ERROR:ex:RuntimeError!
Traceback (most recent call last):
  File "C:\Users\mdriscoll\Desktop\exceptionLogger.py", line 7, in 
    raise RuntimeError
RuntimeError

As you can see, it will log what level is being logged (ERROR), the logger name (ex) and the message we passed to it (RuntimeError!) along with the full traceback. You might find this even handier than using the traceback module.

Creating a Custom Exception

As you write complex programs, you might find a need to create your own exceptions. Fortunately Python makes the process of writing a new exception very easy. Here’s a very simple example:

########################################################################
class ExampleException(Exception):
    pass

try:
    raise ExampleException("There are no droids here")
except ExampleException, e:
    print e

All we did here was subclass the Exception type with a blank body. Then we tested it out by raising the error inside a try/except clause and printed out the custom error message that we passed to it. You can actually do this with any exception that you raise: raise NameError(“This is an improper name!”). On a related note, see pydanny’s article on attaching custom exceptions to functions and classes. It’s very good and shows some neat tricks that makes using your custom exceptions easier without having to import them so much.

Wrapping Up

Now you should know how to catch exceptions successfully, grab the tracebacks and even create your own custom exceptions. You have the tools to make your scripts keep on running even when bad things happen! Of course, they won’t help if the power goes out, but you can cover most cases. Have fun hardening your code against users and their many and varied ways of making your programs crash.

Additional Reading

11 thoughts on “Python 101: Exception Handling”

  1. I’m not sure how you’re going to do anything like reliable transaction rollback without using a “bare” exception. Calling them bad is entirely inappropriate. They’re the correct solution in many situations, more importantly, they’re frequently the only correct solution in those situations.

    Moreover, your rationale for them being “bad” also disqualifies use of finally as well, which is plainly not what you intended.

  2. Certainly, it’s probably worth providing an example of when to use it and an admonishment to only use it in similar cases, it’s not the most common error response. RDBMS Transaction SAVEPOINTs are a great example.

  3. My slight confusion with exceptions is when to use them in your own modules. When to put them in the module or when to leave it to the script that is importing the module.

  4. For me, I almost always put the error handling in the module that needs it. Importing a module and wrapping parts of its calls in exception handling seems a little backwards to me as the module itself should know how to handle its own exceptions.

  5. I added a little paragraph about when bare excepts might be better to use. Of course, I know some people won’t agree and they are welcome to write their code any way they like. I also changed the phrasing from “BAD” to “not recommended”. Thanks for the feedback!

  6. No, because KeyboardInterrupt doesn’t inherit from Exception. Not cleaning up on failure just because Ctrl-C was pressed is poor form in a command-line application.

  7. If the application can be interrupted then you’re likely to want to handle that separately anyway, than say a data or deadlock error. Or you could use except BaseException as exc.

  8. If you’re going to catch BaseException, you might as well do a blanket catch, as I don’t really think it’s worth worrying about string exceptions in Python 2 anymore, and they are effectively the same in Python 3.

    And no, I don’t see why savepoint rollback, or any other valid use of a bare except, should treat KeyboardInterrupt any differently from any other exception. It would help to have an example of what you’re thinking about.

Comments are closed.