Python: Exceptions

This is part of my Python & Django Series which can be found here including information on how to download all the source code.

Code does not always work as intended. Even if the perfect system could exist, it is not possible to protect around every possible situation from badly formed data files, user input or even the network going down.

Expecting errors is an integral part of coding and provides the developer with the ability to respond when an error has occurred. Whether it is to reverse a database transaction, clearing expensive resources or simply logging and reporting the error.

Try Catch

Like most languages, the try catch statement is the basics building block of error handling.

The try statement defines an area where we would like the run time environment to allows us the opportunity to respond to errors being raised.

The catch or expect statement is the code which will run when an error is raised.

The basic syntax looks like this.

Code:

    try:
        # Code
    expect:
        # Error handling code

Any code after the try statement and before the except statement which causes an error, will immediately stop execution and be resumed within the top of the except statement.

After the except statement has run, control will be passed to the fist line outside of the try catch block, allowing the program to carry on as if no error has been raised.

Code:

def convert_to_int(input_value):
    try:
        x = int(input_value)
        print("{0} can be converted into an int of {1}".format(input_value, x))
    except:
        print("An error was caught!:")

for an_input in ["1", "a", "b"]:
    print("nTrying with:", an_input)
    convert_to_int(an_input)

Output:

Trying with: 1
1 can be converted into an int of 1

Trying with: a
An error was caught!

Trying with: b
An error was caught!

Catch An Exception

If we want to find out more about the error raised we can explicitly catch the exception and assign it to a variable. This will allow us to report the exception to the user or the log system as required.

The example above has been modified by catching the exception into the variable called ex in the except statement. In the error handling code we print the exception to the terminal.

Code:

def convert_to_int(input_value):
    try:
        x = int(input_value)
        print("{0} can be converted into an int of {1}".format(input_value, x))
    except Exception as ex :
        print("The following exception was caught:")
        print(ex)

for an_input in ["1", "a", "b"]:
    print("nTrying with:", an_input)
    convert_to_int(an_input)

Output:

Trying with: 1
1 can be converted into an int of 1

Trying with: a
The following exception was caught:
invalid literal for int() with base 10: ‘a’

Trying with: b
The following exception was caught:
invalid literal for int() with base 10: ‘b’

Exception Granularity

The Exception type is a class which can be inherited from. Python ships with many sublcasses of Exception with the intention of code raising an exception which is more specific to the error being raised.

In code we might want to act differently based upon the error being raised. For example if the network is down we might want to retry but if we have bad data from the user we might want to allow the user to re-input the data.

Python allows catching exceptions by their type as well as the general catch statement which have seen above.

We can provide multiple except statements all catching a different exception type which in turn allows us to respond differently based upon the error being raised.

Code:

try:
    f = open('foo.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as err:
    print("IOError: {0}".format(err))
except ValueError as err:
    print("ValueError: {0}".format(err))
except Exception as err:
    print("Exception: {0}".format(err))
except:
    print("Won't ever execute due to the except condition above")

Output:

IOError: [Errno 2] No such file or directory: ‘foo.txt’

An except statement will run if the exception types are the same or the error being raised has the defined exception type in its ancestry; i.e it inherits directly or indirectly from the defined exception type.

Only one exception statement will run so you should be careful to ensure your exceptions are placed from the most specific to the least specific.

The example above catches the Exception type last which will catch all errors being raised as long as they have not already been caught.

Exception Details

Like all types in Python, the exception is a class and as such contains state and behaviour.

We can write the exception summary to a string with the __str__ method which is called from string format or the print command.

The __traceback__ can be used to read the method stack at the time when the exception was raised.

The args property can be used to determine any additional arguments assigned to the exception when it was raised.

Code:

try:
    1 / 0
except Exception as err:
    print("Exception: {0}".format(err))
    print(err)
    print(err.__traceback__)
    print(err.args)

Output:

Exception: division by zero
division by zero

(‘division by zero’,)

Alternatively the sys.exec_info returns a tuple of information about the current exception being handled.

Code:

import sys

try:
    f = open('foo.txt')
    s = f.readline()
    i = int(s.strip())
except:
    print("Catch!!")
    for a_msg in sys.exc_info():
        print(a_msg)

Output:

Catch!!

[Errno 2] No such file or directory: ‘foo.txt’

Try Catch Finally

Python also allows a finally statement with a try catch block. Here the code is called regardless if an exception is raised or not or whether a raised exception was caught.

  • Iteration 1 raises no error.
  • Iteration 2 raises an error which is caught
  • Iteration 3 raises an error which is not caught.

After the catch statement has run for iteration three the program is terminated due to an exception not being caught.

Code:

def raise_if_true(arg_input):
    try:
        if arg_input == 2:
            raise ValueError("Input was 2")
        elif arg_input == 3:
            raise Exception("Input was 3")
    except ValueError as exception:
        print("Caught:", exception)
    finally:
        print('This is the finally!!!!')

for number in [1, 2, 3]:
    raise_if_true(number)

Output:

This is the finally!!!!
Traceback (most recent call last):
File “/home/lukey/Dropbox/Development/SandBox/Git/ThePythonPit/PythonSandBox/Exceptions/try_finally_example.py”, line 18, in
Caught: Input was 2
raise_if_true(number)
This is the finally!!!!
File “/home/lukey/Dropbox/Development/SandBox/Git/ThePythonPit/PythonSandBox/Exceptions/try_finally_example.py”, line 11, in raise_if_true
This is the finally!!!!
raise Exception(“Input was 3”)
Exception: Input was 3

Try Catch Finally Else

The else statement can be added onto a try statement to allow an area of code which will be run if no error is raised.

All variables are accessible to the try statement are available in the else statement. Here we assign the result to a variable called result which is created in the try block, we then access this within the else statement.

Code:

def divide(x, y):
    try:
        print("nPerforming: {0} / {1}".format(x, y))
        result = x / y
    except ZeroDivisionError:
        print("division by zero!")
    else:
        print("Result =", result)
    finally:
        print("Executing the finally clause")

divide(1, 2)
divide(1, 0)

Output:

Performing: 1 / 2
Result = 0.5
Executing the finally clause

Performing: 1 / 0
division by zero!
Executing the finally clause

Re-Throwing An Exception

We can re-throw an exception after we have finished handling it. This can be useful if we want the program to finish executing or we would like an outer try catch block to also catch and respond to the error.

We re-throw an error with the raise keyword.

In the following example we re-throw an exception which has been caught. As the try statement is not nested the program will terminate immediately.

Code:

try:
    f = open('foo.txt')
    s = f.readline()
    i = int(s.strip())
except:
    print("Caught!!")
    raise

print("This won't print!!!")

Output:

Caught!!
Traceback (most recent call last):
File “/home/lukey/Dropbox/Development/SandBox/Git/ThePythonPit/PythonSandBox/Exceptions/rethrowing_an_exception.py”, line 6, in
f = open(‘foo.txt’)
FileNotFoundError: [Errno 2] No such file or directory: ‘foo.txt’

Raising An Exception

There might be times in your code where you want to raise an exception to trigger common error handling code which exists higher up in the method stack.

An error can be raised by simply creating an instance of the Exception class or any class which inherits from Exception along with the raise command.

The Exception class gathers all constructor arguments and places them into the args collection.

Code:

try:
    raise Exception('spam', 'eggs')
except Exception as inst:
    print(inst)
    print(inst.args)

Output:

(‘spam’, ‘eggs’)
(‘spam’, ‘eggs’)

Sublassing Exceptions

Any class which has the Exception type within its ancestry can be raised and caught in Python.

Inheriting from Exception allows catching to be granular as we have seen previously but it also allows us to add state and behaviour onto an exception.

In the following example we subclass exception to allow a field called value to be set during error raising and read during the error handling.

Code:

class MyError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)

try:
    raise MyError(2 * 2)
except MyError as e:
    print(e.value)
    print(e)

Output:

4
4

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s