6. Exceptions
We will now look at exceptions.
![]() |
The first script illustrates the need to handle exceptions.
We intentionally cause an error to see the information produced by the interpreter. The output is as follows:
Line 4 gives us:
- the exception type: ZeroDivisionError;
- the associated error message: integer division or modulo by zero. It is in English. This is something you might want to change.
The essential rule in programming is that we must do everything possible to avoid "uncontrolled" crashes like the one above. Even in the event of an error, the program must terminate properly by providing information about the error that occurred.
The syntax for exception handling is as follows:
try:
actions that may throw an exception
except [exception class, ...]:
actions to handle the exception
finally:
actions that are always executed, regardless of whether an exception occurs
In the try block, execution of the actions stops as soon as an exception occurs. In this case, execution continues with the actions in the except clause.
The
except [MyException, ...]:
intercepts exceptions of type MyException or derived types. When an exception occurs in a try block, the interpreter examines the except clauses associated with the try block in the order they were written. It stops at the first except clause capable of handling the exception that occurred. If it finds none, the exception is propagated back to the calling method. If that method has a try/except block, the exception is handled again; otherwise, it continues to propagate up the chain of called methods. Ultimately, it reaches the Python interpreter. The interpreter then terminates the running program and displays an error message of the type shown in the previous example. The rule is therefore that the main program must catch all exceptions that may propagate from called methods.
An exception carries information about the error that occurred. This information can be retrieved using the following syntax:
except MyException as information:
information is a tuple that carries the information related to the exception.
The syntax
except MyException, error:
assigns the error message associated with the exception to the variable error.
To throw an exception, use the syntax
raise MyException(param1, param2, ...)
where MyException is most often a class derived from the Exception class. The parameters passed to the class constructor will be available to the except clause of exception-handling structures.
These concepts are illustrated by the following script.
The following script explicitly handles errors:
# -*- coding=utf-8 -*-
i=0
# we trigger an error and handle it
x=2
try:
x = 4/0
except ZeroDivisionError, error:
print("%s: %s") % (i, error)
# the value of x has not changed
print "x=%s" % (x)
# let's try again
i+=1
try:
x=4/0
except Exception, error:
# we catch the most general exception
print ("%s: %s ") % (i, error)
# we can catch multiple types of exceptions
i+=1
try:
x = 4/0
except ValueError, error:
# This exception does not occur here
print("%s: %s") % (i, error)
except Exception, error:
# we catch the most general exception
print("%s: (Exception) %s") % (i, error)
except ZeroDivisionError, error:
# catch a specific type
print("%s: (ZeroDivisionError) %s") % (i, error)
# Let's try again, changing the order
i+=1
try:
x = 4/0
except ValueError, error:
# this exception does not occur here
print("%s: %s") % (i, error)
except ZeroDivisionError, error:
# we catch a specific type
print("%s: (ZeroDivisionError) %s") % (i, error)
except Exception, error:
# catch the most general exception
print("%s: (Exception) %s") % (i, error)
# An except clause without arguments
i+=1
try:
x = 4/0
except:
# We are not interested in the nature of the exception or the error message
print("%s: There was a problem") % (i)
# another type of exception
i+=1
try:
x = int("x")
except ValueError, error:
print("%s: %s") % (i, error)
# An exception carries information in a tuple accessible to the program
i+=1
try:
x = int("x")
except ValueError as info:
print("%s: %s") % (i, info)
# exceptions can be raised
i+=1
try:
raise ValueError("param1", "param2", "param3")
except ValueError as info:
print("%s: %s") % (i, infos)
# You can create your own exceptions
class MyError(Exception):
pass
# we raise the MyError exception
i+=1
try:
raise MyError("info1", "info2", "info3")
except MyError as info:
print("%s: %s") % (i, infos)
# we raise the MyError exception
i += 1
try:
raise MyError("my error message")
except MyError, error:
print("%s: %s") % (i, error)
# You can raise any type of object
class Object:
def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
i+=1
try:
raise Object("pb...")
except Object as error:
print "%s: %s" % (i, error)
# the finally clause is always executed
# whether or not an exception occurs
i+=1
x = None
try:
x = 1
except:
print "%s: exception" % (i)
finally:
print "%s: finally x=%s" % (i, x)
i+=1
x = None
try:
x = 2/0
except:
print "%s: exception" % (i)
finally:
print "%s: finally x=%s" % (i, x)
Notes:
- lines 6–9: handle a division by zero;
- line 8: we catch the exact exception that occurs;
- line 11: because of the exception that occurred, x did not receive a value and therefore did not change;
- lines 15-19: we do the same thing but catch a higher-level exception of type Exception. Since the ZeroDivisionError exception derives from the Exception class, the except clause will catch it;
- lines 23–33: we include multiple except clauses to handle multiple exception types. Only one except clause will be executed, or none at all if the exception does not match any except clause;
- lines 51–55: The except clause may have no arguments. In this case, it catches all exceptions;
- lines 59–62: introduce the ValueError exception;
- lines 66–69: retrieve the information carried by the exception;
- lines 73–76: introduce how to raise an exception;
- lines 79–84: illustrate the use of a custom exception class, MyError;
- lines 79–80: the MyError class simply inherits from the base Exception class. It adds nothing to its base class. But now, it can be explicitly named in except clauses;
- lines 97–107: demonstrate that in Python, you can actually throw any type of object, not just objects derived from the Exception class;
- Lines 109–127: demonstrate the use of the finally clause.
The screen output is as follows:
0: integer division or modulo by zero
x=2
1: integer division or modulo by zero
2: (Exception) integer division or modulo by zero
3: (ZeroDivisionError) integer division or modulo by zero
4: An error occurred
5: invalid literal for int() with base 10: 'x'
6: invalid literal for int() with base 10: 'x'
7: ('param1', 'param2', 'param3')
8: ('info1', 'info2', 'info3')
9: my error message
10: problem...
11: finally x=1
12: exception
12: finally x=None
This new script illustrates how exceptions are propagated up the chain of calling methods:
# -*- coding=utf-8 -*-
# a custom exception
class MyError(Exception):
pass
# three methods
def f1(x):
# We don't handle exceptions—they are automatically propagated
return f2(x)
def f2(y):
# We don't handle exceptions—they are automatically propagated
return f3(y)
def f3(z):
if (z % 2) == 0:
# if z is even, raise an exception
raise MyError("exception in f3")
else:
return 2*z
#---------- main
# Exceptions propagate up the chain of called methods
# until a method catches it. Here, it will be main
try:
print f1(4)
except MyError as info:
print "type: %s, arguments: %s" % (type(infos), infos)
# A method can enrich the exceptions it raises
def f4(x):
try:
return f5(x)
except MyError as infos:
# we enrich the exception and then re-raise it
raise MyError(infos, "exception in f4")
def f5(y):
try:
return f6(y)
except MyError as info:
# we add details to the exception and then re-raise it
raise MyError(infos, "exception in f5")
def f6(z):
if (z % 2) == 0:
# raise an exception
raise MyError("exception in f6")
else:
return 2*z
#---------- main
try:
print f4(4)
except MyError as info:
print "type: %s, arguments: %s" % (type(infos), infos)
Notes:
- Lines 27–30: In the call main --> f1 --> f2 --> f3 (line 28), the MyError exception thrown by f3 will propagate up to main. It will then be handled by the except clause on line 29;
- lines 55–58: in the call main → f4 → f5 → f6 (line 56), the MyError exception thrown by f6 will propagate up to main. It will then be handled by the except clause on line 29. This time, as it propagates up the chain of calling methods, the MyError exception is enriched with information added by each method along the way.
The screen output is as follows:
