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

Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary