Exception Handling in Python

Exception handling is a mechanism which allows us to handle errors gracefully while the program is running instead of abruptly ending the program execution.

Runtime Errors

Runtime errors are the errors which happens while the program is running. Note is that runtime errors do not indicate there is a problem in the structure (or syntax) of the program. When runtime errors occurs Python interpreter perfectly understands your statement but it just can’t execute it. However, Syntax Errors occurs due to incorrect structure of the program. Both type of errors halts the execution of the program as soon as they are encountered and displays an error message (or traceback) explaining the probable cause of the problem.

The following are some examples of runtime errors.

Example 1: Division by a zero.

Example 2: Adding string to an integer.

Example 3: Trying to access element at invalid index.

Example 4: Opening a file in read mode which doesn’t exists.

Again note that all the above statements are syntactically valid, the only problem is that when Python tries to execute them, they got into an invalid state.

An error which occur while the program is running is known as exception. When this happens, we say Python has raised an exception or an exception is thrown. Whenever such errors happens, Python creates a special type of object which contains all the relevant information about the error just occurred. For example, it contains line number at which error occurred, error messages (remember it is called traceback) and so on. By default, these errors simply halts the execution of the program. Exception handling mechanism allows us to deal with such errors gracefully without halting the program.

try-except statement

In Python, we use try-except statement for exception handling. Its syntax is as follows:

The code begins with the word try, which is a reserved keyword, followed by a colon (:). In the next line we have try block. The try block contains code that might raise an exception. After that we have an except clause that starts with the word except, which is again a reserved keyword, followed by exception type and a colon (:). In the next line we have a except block. The except block contains code to handle the exception. As usual, code inside the try and except block must be properly indented, otherwise you will get an error.

Here is how try-except statement executes:

When an exception occurs in the try block, execution of the rest of the statements in the try block is skipped. If the exception raised matches the exception type in the except clause, the corresponding handler is executed.

If the exception raised in the try block block doesn’t matches with the exception type specified in the except clause the program halts with an a traceback.

On the other hand, If no exception is raised in the try block, the except clause is skipped.

Let’s take an example:

python101/Chapter-19/exception_handling.py

Run the program and enter 0.

First run output:

In this example, the try block in line 3 raises a ZeroDivisionError. When exception occurs Python looks for the except clause with the matching exception type. In this case, it finds one and runs the exception handler code in that block. Notice that because exception is raised in line 3, the execution of reset of the statements in the try block is skipped.

Run the program again but this time enter a string instead of a number:

Second run output:

This time our program crashes with the ValueError exception. The problem is that the built-in int() only works with strings that contains numbers only, if you pass a string containing non-numeric character it will throw ValueError exception.

As we have don’t have except clause with the ValueError exception, our program crashes with ValueError exception.

Run the program again and this time enter a integer other than 0.

Third run output:

In this case, statements in the try block executes without throwing any exception, as a result the except clause is skipped.

Handling Multiple Exceptions

We can add as many except clause as we want to handle different types of exceptions. The general format of such a try-except statement is as follows:

Here is how it works:

When exception occurs, Python matches the exception raised against the every except clause sequentially. If a match is found then the handler in the corresponding except clause is executed and rest of the of the except clauses are skipped.

In case the exception raised doesn’t matches any except clause before the last except clause (line 18), then the handler in the last except clause is executed. Note that the last except clause doesn’t have any exception type in front of it, as a result it can catch any type of exception. Off course, this last except clause is entirely optional but we commonly use it as a last resort to catch the unexpected errors and thus prevent the program from crashing.

The following program demonstrates how to use multiple except clauses.

python101/Chapter-19/handling_muliple_types_of_exceptions.py

First run output:

Second run output:

Third run output:

Here is another example program which asks the user to enter the filename and then prints the content of the file to the console.

python101/Chapter-19/exception_handling_while_reading_file.py

Run the program and specify a file a which doesn’t exists.

First run output:

Run the program again and this time specify a file which you don’t have permission to read.

Second run output:

Run the program once more but this time time specify a file which does exists and you have the permission to read it.

Third run output:

The else and finally clause

A try-except statement can also have an optional else clause which only gets executed when no exception is raised. The general format of try-except statement with else clause is as follows:

Here is a rewrite of the above program using else clause.

python101/Chapter-19/else_clause_demo.py

Run the program and enter a file which doesn’t exists.

First run output:

Again run the program but this time enter a file which does exists and you have the permission to access it.

Second run output:

As expected, the statement in the else clause is executed this time. The else clause is usually used to write code which we want to run after the code in the try block ran successfully.

Similarly, we can have a finally clause after all except clauses. The statements under the finally clause will always execute irrespective of whether the exception is raised or not. It’s general form is as follows:

The finally clause is commonly used to define clean up actions which must be performed under any circumstance. If try-except statement has an else clause then finally clause must appear after it.

The following program shows finally clause in action.

python101/Chapter-19/finally_clause_demo.py

First run output:

Second run output:

Exceptions Propagation and Raising Exceptions

In the earlier few sections we have learned how to deal with exceptions using try-except statement. In this section, we will discuss who throws an exception, how to create an exception and how they propagate.

An exception is simply an object raised by a function signaling that something unexpected has happened which the function itself can’t handle. A function raises exception by creating an exception object from an appropriate class and then throws the exception to the calling code using the raise keyword as follows:

We can raise exceptions from our own functions by creating an instance of RuntimeError() as follows:

When an exception is raised inside a function and is not caught there, it is automatically propagated to the calling function (and any function up in the stack), until it is caught by try-except statement in some calling function. If the exception reaches the main module and still not handled, the program terminates with an error message.

Let’s take an example.

Suppose we are creating a function to calculate factorial of a number. As factorial is only valid for positive integers, passing data of any other type would render the function useless. We can prevent this by checking the type of argument and raising an exception if the argument is not a positive integer. Here is the complete code.

python101/Chapter-19/factorial.py

Output:

Notice that when factorial() function is called with a string argument (line 17), a runtime exception is raised in line 3. As factorial() function is not handling the exception, the raised exception is propagated back to the main module where it is caught by the except clause in line 18.

Note that in the above example we have coded try-except statement outside the factorial() function but we could have easily done the same inside the factorial() function as follows.

python101/Chapter-19/handling_exception_inside_factorial_func.py

Output:

However, this is not recommended. Generally, the called function throws an exception to the caller and it’s the duty of the calling code to handle the exception. This approach allows us to to handle exceptions in different ways, for example, in one case we show an error message to the user while in other we silently log the problem. If we were handling exceptions in the called function, then we would have to update the function every time a new behavior is required. In addition to that, all the functions in the Python standard library also conforms to this behavior. The library function only detects the problem and raises an exception and the client decide what it needs to be done to handle those errors.

Now Let’s see what happens when an exception is raised in a deeply nested function call. Recall that if an exception is raised inside the function and is not caught by the function itself, it is passed to its caller. This process repeats until it is caught by some calling function down in the stack. Consider the following example.

The program execution starts by calling the main() function. The main() function then invokes function1(), function1() invokes function2(), finally function2() invokes function3(). Let’s assume function3() can raise different types of exceptions. Now consider the following cases.

  1. If the exception is of type ExceptionType4, statement7 is skipped and the except clause in line 7 catches it. The execution of function3() proceeds as usual and statement8 is executed.
  2. If exception is of type ExceptionType3, the execution of function3() is aborted (as there is no matching except clause to handle the raised exception) and the control is transferred to the caller i.e function2() where ExceptionType3 is handled by the except clause in line 17. The statement5 in function2() is skipped and statement6 is executed.
  3. If the exception is of type ExceptionType2, function3() is aborted and the control is transferred to the function2(). As function2() doesn’t have any matching except clause to catch the exception, its execution is aborted and control transferred to the function1() where the exception is caught by the except clause in line 26. The statement3 in function1() is skipped and statement4 is executed.
  4. If the exception is of type ExceptionType1, then control is transferred to the function main() (as function3(), function2() and function1() doesn’t have matching except clause to handle the exception) where the exception is handled by except clause in line 35. The statement1 in main() is skipped and statement2 is executed.
  5. If the exception is of type ExceptionType0. As none of the available functions have the ability to handle this exception, the program terminates with an error message.

Accessing Exception Object

Now we know how to handle exceptions as well throw them whenever needed. One thing we didn’t yet cover is how to access exception object thrown by the function. We can access exception object using the following form of except clause.

From now on, whenever except clause catches an exception of type ExceptionType it assigns the exception object to the variable e.

The following example demonstrates how to access an exception object:

python101/Chapter-19/accessing_exception_object.py

Output:

Notice that the error message printed by exception object (line 21) is the same which we passed while creating creating RuntimeError object (line 3).

Creating Your Own Exceptions

So far in this chapter we have been using built-it exception classes such as ZeroDivisionError, ValueError, TypeError, RuntimeError etc. Python also allows you to create new exception classes to cater you own specific needs. The BaseException class is the root of all exception classes in Python. The following figure shows exception class hierarchy in Python.

exception class hierarchy

We can create our own exception class by deriving it from Exception built-in class. The following examples shows how to create a custom exception.

python101/Chapter-19/InvalidFactorialArgumentException.py

python101/Chapter-19/factorialWithCustomException.py

Output:

Leave a Comment