Screenshot CONTENTS Screenshot

Errors and Exceptions

Java graphics chic01.gif This chapter details Jython's built-in exceptions and related mechanisms. The topic of exception objects also invites a closer study of the try/ except statement, raise statement, traceback object, assert statement, _ _debug__ variable, and the recently added warnings framework.

Jython Exceptions

Jython's exceptions are defined in the class org.python.core.exceptions. The source for this class contains a hierarchy of the exceptions that is shown in Screenshot. Each exception's corresponding message string is included following the class name in this image.

Screenshot Jython's exception hierarchy.

Java graphics 03fig01.gif


You cannot divide a number by zero. You cannot access a name that does not exist, and you cannot use more memory than your machine has. The Jython interpreter enforces such edicts by raising an exception whenever something tries to break these rules. Jython version 2.1 infuses some gray into this black-and-white world with the addition of warnings. The warnings you see in Screenshot are exceptions, but they introduce something unique because of the warnings framework, discussed later in this chapter. The simplest way to study Jython's exceptions is to try to create an exceptional situation in Jython's interactive interpreter. Below is an example of some errors and the exceptions raised because of the error. You cannot access a variable that is not defined:

>>> print x Traceback (innermost last): File "<console>", line 1, in ? NameError: x 


You cannot access a list index beyond the range of the list:

>>> L = [] >>> print L[1] Traceback (innermost last): File "<console>", line 1, in ? IndexError: index out of range: 1 


You cannot call a non-existent method:

>>> L.somemethod() # Calling a non-existent method name is an error Traceback (innermost last): File "<console>", line 1, in ? AttributeError: 'list' object has no attribute 'somemethod' 


Indention matters in Jython, as seen in this loop:

>>>for x in range(10): ... i = x # This line is indented one tab ... print i # This line is indented 5 spaces Traceback (innermost last): (no code object) at line 0 File "<console>", line 3 print i ^ SyntaxError: inconsistent dedent 


Java exceptions can also be raised in Jython. This occurs when an imported Java class throws an exception within the Java code. This exception is reported in Jython as the Java exception. Listing 3.1 shows a simple example of this.

Listing 3.1 A Java Exception Within Jython
//file: Except.java public class Except {
 public Except() {} public void test() throws IllegalAccessException {
 throw new IllegalAccessException("Just testing"); } } >>>#Using the above Java class in Jython >>> import Except >>> e = Except() >>> e.test() Traceback (innermost last): File "<console>", line 1, in ? java.lang.IllegalAccessException: just testing at Except.test(Except.java:4) ... java.lang.IllegalAccessException: java.lang.IllegalAccessException: Just testing 


Exception Handling

In Java terminology, an exception is thrown when an error occurs. This thrown exception propagates until something "catches" it. In Jython lingo, exceptions are "raised" instead of thrown. When a Jython exception is raised, the flow of the program changes to a search for exception handlers. This searching not only includes the scope in which the exception occurred, but propagates through the stack levels looking for an appropriate except clause. If an appropriate except clause does not exist, it halts the program and displays the exception with traceback information.

Jython 2.1 and sys. excepthook

New in Python 2.1 is sys.excepthook, which you can set to a callable object. although this feature is not in Jython at the time of this writing, it is likely that it will exist by the time you read this. What this means is that when an exception finds no appropriate except clause to handle it, it is passed to sys. excepthook before halting the program. That way, whatever you assign to sys.excepthook can do anything you want with the exception before program termination. This is valuable for closing and flushing resources before program termination.


The try statement in Jython has two formats. The first is the try/except/else, which has the following syntax:

"try:" code-block "except" [expression ["," target]] ":" code-block ["else:" code-block] 


Listing 3.2 shows an example usage of this syntax.

Listing 3.2 The try/except/else Syntax
>>> try: ... print a # a is not defined, so is a NameError ... except SyntaxError: ... print "I caught a SyntaxError" ... except NameError: ... print "I caught a NameError" ... except: ... print "A plain except catches ANY type of exception" ... else: ... print "no exception was raised in the try block ... I caught a NameError 


The flow of execution in Listing 3.2 starts with the block of code within the try. If an exception is raised, except blocks are searched for the first one to handle the specific type of exception raised or an except without a specified exception type (it is generic and handles any exception). If no exception is raised within the try block, execution continues with the else block. The except half of the try/except can have two optional elements before its code block. The first is the class of exception that the associated block of code is to handles. Listing 3.2 specifies an exception class for all but the last except clause. Listing 3.2 does not make use of the second optional element—the target element. The target receives the exception's parameter value if provided. A brief, pre-emptive look at the raise statement shows what is meant by the exception's "parameter value":

>>> try: ... raise SyntaxError, "Bad Syntax" ... except SyntaxError, target: ... print target ... Bad Syntax 


The string Bad Syntax is the parameter given the SyntaxError class when raising the exception. Therefore, this would also be what the variable target is bound to in the except clause. Screenshot not only shows Jython's exceptions and their message strings, it also shows the hierarchy of exceptions. To handle an OverflowError, ZeroDivisionError, or FloatingPointError, you only need to handle their super class— ArithmeticError. Handling the StandardError exception would cover most errors, and handling the base class Exception should handle all exceptions except user-defined ones that do not inherit from Exception as is recommended. Listing 3.3 shows how the class hierarchy of exceptions allows you to handle a group of exceptions by handling their common base class.

Listing 3.3 Base Classes in the Except Clause
>>> try: ... 1/0 ... except ArithmeticError, e: ... print "Handled ArithmeticError- ", e ... Handled ArithmeticError- integer division or modulo >>> >>> L = [1,2,3] >>> try: ... print L[10] # this index does not exist ... except LookupError, e: ... print "Handling LookupError- ", e ... Handling LookupError- index out of range: 10 


The second format is the try/finally. This executes the try block of code, and whether an exception is raised or not, the finally block of code is executed.The syntax for this is as follows:

"try:" code-block "finally:" code-block 


Listing 3.4 shows an example of try/finally that uses the finally block to close a file object regardless of what happens in the try block.

Listing 3.4 Using try/finally to Ensure that a File Is Closed
>>> fileA = open("datafile", "w") >>> try: ... fileB = open("olddata", "r") ... print >>fileA, fileB.read() ... finally: ... fileA.close() ... if "fileB" in vars(): ... fileB.close() ... 


Another valuable trick in Listing 3.4 is testing if the file is in vars() before trying to close it. If the open() function failed, there is no fileB to close, so the testing for existing in vars() prevents a second exception from being raised within the finally block by trying to close a non-existing file object. Listing 3.5 demonstrates nesting try/except statements. In this listing, only the outermost try statement has an appropriate except expression, so this outer level is what handles the exception. Another interesting note about Listing 3.5 is the use of the sys.exit() function. You must first import the sys module before using this function, and what this actually does is raise the SystemExit exception. This means if you catch the SystemExit exception in the except clause, the system does not exit.

Listing 3.5 Nested try Statements
>>> import sys >>> >>> try: ... try: ... try: ... sys.exit() # This raises the "SystemExit" exception ... except ValueError: ... pass ... except SyntaxError: ... pass ... except SystemExit: ... print "SystemExit exception caught- not exiting." ... SystemExit exception caught- not exiting. 


Listing 3.1 showed a Java program that throws a java.lang.IllegalAccessException. This raises the issue of how Java specific exceptions are handled (caught) in Jython. Listing 3.6 has a revised version of the Jython code that appeared in Listing 3.1. A try/except has been added with an except clause that specifically handles the java.lang.IllegalAccessException thrown in the java class. To handle a Java exception you can either use an empty except clause so that it is generic and handles any exception, or you can use import java before the exception and provide the specific exception's package and class in the except clause that is designed to handle the exception or its subclasses.

Listing 3.6 A Java Exception Handled in Jython
//file: Except.java public class Except {
 public Except() {} public void test() throws IllegalAccessException {
 throw new IllegalAccessException("Just testing"); } } >>> #Using the above Java class in Jython >>> import Except # import the exception-throwing Java class >>> import java >>> try: ... e = Except() ... e.test() ... except java.lang.IllegalAccessException: ... print "Caught IllegalAccessException" ... Caught IllegalAccessException 


raise Statement

Explicitly raising an exception where one would not normally occur may be desirable at times in Jython. To do this, use the raise statement. An example situation where this is useful is checking parameter types. Jython does not have compile-time checking for this in the 2.1 or earlier versions. This means that if a parameter must be a certain type, the code must test it at runtime and use the raise statement for inappropriate types. Listing 3.7 does exactly that. The function listComplement requires two lists or tuples for parameters. The parameters' types are tested before any other processing, and a TypeError exception is raised if either parameter is not a list or tuple.

Interfaces and Jython

Jython 2.1 and earlier does not have parameter, protocol, interface, or type checking. Python's "types" special interest group is churning with proposals related to these topics. Substantial thought, research, and hard work have resulting in a few Python enhancement proposals that could likely change this in future releases. Check for this on Jython's website or the website associated with this tutorial.


Listing 3.7 determines the set complement of two lists. The complement of list 1 (L1) relative to list 2 (L2) is all elements in L2 that are not in L1. An optimization note is that list 1 (L1) is converted into dictionary keys so that the fast has_key can be used instead of the sequence member test, in. The types module is imported in Listing 3.7 to compare parameter types with the types that are required by the listComplement function (TupleType and ListType).

Listing 3.7 Jython's raise Statement
# file sets.py # import cononical types to compare with parameter types from types import ListType, TupleType def listComplement(S1, S2): if not (type(S1) == ListType or type(S1) == TupleType and type(S2) == ListType or type(S2) == TupleType): raise TypeError, "Only lists and tuples are supported." D = {} for x in S1: D[x] = 1 # Convert S1 to dictionary keys. complement = [] # An empty list to hold results. for item in S2: if not D.has_key(item): complement.append(item) return complement # Test the function with lists list1 = range(0, 100, 3) list2 = range(0, 100, 7) resultSet = listComplement(list1, list2) print "Complement of list1 relative to list2: ", resultSet # Now Test the function with a list and a numeric type notalist = 5 resultSet = listComplement(notalist, list2) 


Results from running jython sets. py at a command line:

Complement of list1 relative to list2: [7, 14, 28, 35, 49, 56, 70, 77, 91, 98] Traceback (innermost last): File "sets.py", line 26, in ? File "sets.py", line 8, in listComplement TypeError: Only lists and tuples are supported. 


Listing 3.7 is longer than the average interactive example, and it is better to place its contents in a file that can be ran with the command-line jython sets.py. The results from running the sets.py script show how the listComplement function raises a TypeError for inappropriate parameters. The raise statement in Listing 3.7 uses two expressions, but there are actually three optional expressions in the raise statement's syntax. The syntax for the raise statement is as follows:

"raise" [expression ["," expression ["," expression]]] 


Substituting names of what the expressions represent makes it read like this:

"raise" [type ["," value ["," traceback]]] 


The type can be the actual class of the exception, a string, or an instance of the exception class raised. Listing 3.7 uses the actual TypeError class to raise the error. However, it could have used an instance of TypeError to raise the same exception as demonstrated in Listing 3.8. Using a string for the type in a raise statement raises a string-based exception. String-based exceptions are legal, but are discouraged in favor of class or instance exceptions instead.

Listing 3.8 Different Ways to Specify the type of a Raised Exception
>>> # The first way is the actual class. >>> # Use the built-in function "type()" to confirm it is a class. >>> type(TypeError) <jclass org.python.core.PyClass at 7907289> >>> raise TypeError Traceback (innermost last): File "<console>", line 1, in ? TypeError: >>> >>> # The second way is a string. >>> raise "A String-based exception" Traceback (innermost last): File "<console>", line 1, in ? A String-based exception >>> >>> # The third way is an instance. >>> Err = TypeError() # Create the instance. >>> type(Err) # Confirm it is an instance. <jclass org.python.core.PyInstance at 2735779> >>> raise Err # Raise the instance Traceback (innermost last): File "<console>", line 1, in ? TypeError: 


The value, or second expression of the raise statement, is a constructor parameter for the exception class. Looking back at Listing 3.7, we see the class TypeError is instantiated with the value Only lists and tuples are supported. This means the following line:

>>>Err = TypeError("Only lists and tuples are supported") >>>raise Err 


is the same as is the same as this line:

>>>raise TypeError, "Only lists and tuples are supported" 


It is a special case when the type, first expression in the raise statement, is an instance. The value expression is a constructor parameter, but if the class is already constructed (an instance), there is nothing to do with the value. The rule in this case (which may already be obvious) is that if the type of exception is an instance, the value expression must be None. The number of parameters the exception's constructor gets depends on whether the value expression is a tuple. A list, dictionary, number, or anything except a tuple is passed as only one parameter. A tuple, on the other hand, is passed as a parameter list. This means there are len(T) parameters for a value of tuple T. Listing 3.9 imports a class called MyException that does nothing except print out the number of parameters passed to its constructor. Listing 3.9 includes a new import statement: from MyException import exceptionA. This imports the exceptionA class from the module MyException. With this import syntax, exceptionA becomes the name bound in the namespace, and it is the exception class used in Listing 3.9.

Listing 3.9 Exception Parameters
>>> from MyException import exceptionA >>> raise exceptionA, (1,2,3,4,5,6,7,8,9) Number of parameters = 9 <-- 9 parameters,, all PyInteger objects. Traceback (innermost last): File "<console>", line 1, in ? exceptionA: <MyException.exceptionA instance at 1669053> >>> >>> # Now try a list instead of a tuple >>> raise exceptionA, [1,2,3,4,5,6,7,8,9] Number of parameters = 1 <-- Only one parameter, a PyList object. Traceback (innermost last): File "<console>", line 1, in ? exceptionA: <MyException.exceptionA instance at 1075173> # MyException.py source class exceptionA(Exception): """An exception class for testing contstructor parameters""" def __init__(self, args): #'*args' catches all remaining arguments print "Number of parameters =", len(args) 


The __init__ method in Listing 3.9 contains a tricky parameter: *args. The asterisk allows this to catch all remaining arguments, which is the mechanism that allows the number of parameters test to work properly. More detailed discussion of the * parameter sytnax appears in ,"User-Defined Functions and Variable Scoping." If the optional third expression exists in the raise statement, it must be a traceback object. See the next section concerning tracebacks for more information. What if there are no expressions provided to the raise statement? This re-raises the exception last raised within the same scope, and is demonstrated in Listing 3.10.

Listing 3.10 raise Without any Expressions
>>> # First, define a function so the two exception can >>> # be within the same local scope >>> def test(): ... try: ... raise SyntaxError, "Bad Syntax" ... except: ... pass ... print "passed first raise" ... raise ... >>> # Call the function to see how the second "raise" works >>> test() passed first raise Traceback (innermost last): File "<console>", line 1, in ? File "<console>", line 3, in test SyntaxError: Bad Syntax 


Tracebacks

When an exception occurs, the message you see begins with a traceback. Also, when you raise an exception, the optional third expressions must evaluate to a traceback object. The traceback object helps unravel the steps taken to the point where an exception was raised. The traceback object is accessible with sys.exc_info( ) when you are within an exception handler (the except or finally after a try). Three items are returned from sys.exc_info(), and they can be paraphrased as type, value, and traceback. This is similar to the order of expressions after the raise statement. A traceback module also exists and is helpful in working with tracebacks. Listing 3.11 ties these items together by raising a exception, using the sys.exc_info( ) method to get the traceback information, and then uses the exc_ lineno and extract_tb functions within the traceback module to print the line number where the exception was raised, and to print the entire traceback. The function sys. exc_ info actually returns a tuple of length three. Jython allows you to unpack tuples on assignment, so this line supplies three identifiers that catch each of the tuple elements.

Listing 3.11 Working with traceback
# file: raise.py import sys import traceback try: raise SyntaxError, "Bad syntax" except: exc_type, exc_value, exc_tb = sys.exc_info() print "The type of exception is:", exc_type print "The value supplied to the exception was", exc_value print "The type of exc_tb is:", type(exc_tb) print "The line where the exceptions was raised is", print traceback.tb_lineno(exc_tb) print "The (filename, line number, function, statement, value) is:" print traceback.extract_tb(exc_tb) The results from running "jython raise.py" are: The type of exception is: exceptions.SyntaxError The value supplied to the exception was Bad syntax The type of exc_tb is: org.python.core.PyTraceback The line where the exceptions was raised is 6 The (filename, line number, function, statement, value) is: [('raise.py', 6, '?', 'raise SyntaxError, 'Bad syntax"')] 


assert Statement and the __debug__ Variable

Another way to raise an exception is with the assert statement. Jython's assert statement assists in debugging programs by testing for certain user-specified conditions and raising an AssertionError exception accordingly. The syntax for the assert statement is as follows:

"assert" expression ["," expression] 


If the first expression evaluates to true, nothing is done; if false, an AssertionError is raised. The optional second expression is the value passed to the AssertionError exception. Listing 3.12 requests user input then uses the assert statement to ensure that the user actually entered data.

Listing 3.12 Testing User Input with assert
>>> def getName(): ... name = raw_input("Enter your name: ") ... assert len(name), "No name entered! program halted" ... print "You entered the name %s" % name ... >>> getName() Enter your name: Robert You entered the name Robert >>> getName() Enter your name: Traceback (innermost last): File "<console>", line 1, in ? File "<console>", line 3, in getName AssertionError: No name entered! program halted >>> >>> # assert's behavior changes when __debug__ = 0 >>> __debug__ = 0 >>> # With __debug__ off, the AssertionError is never raised >>> getName() Enter your name: You entered the name >>> 


Because the assert statement is designed as a debugging tool, assert statements are only evaluated when the internal variable _ _debug_ _ is equal to 1. In the current release of Jython, this _ _debug_ _ variable is a bit of a quandary. It should be a read-only variable that is set at Jython's startup, and Jython should have a -0 switch, meaning optimize, which sets the _ _debug_ _ variable equal to 0. Current releases, however, do not have a -0 switch, and to disable assert statements in fully debugged code, you must assign 0 to the _ _debug_ _ variable (which isn't read-only either). This may have changed by this tutorial's release date, and if it has, information regarding this will be found at the website associated with this tutorial. Listing 3.12 demonstrates assigning to the _ _debug__ variable and how the assert statement is no longer evaluated when _ _debug__ == 0.

Warnings Framework

Warnings allow Python to gently evolve. Instead of suddenly facing an exception in old code as soon as you upgrade to a newer version of Jython, a warning is issued for at least a year's worth of releases to ensure that any deprecation or altered behavior is never a surprise. The warnings framework is new in Jython 2.1. What is the difference between an exception and a warning? Warnings are not fatal. You could receive innumerable warnings without the process being terminated. Built-in functions or statements specific to warnings do not exist as they do for exceptions. To work with warnings, you must import the warnings module. Warnings do not "propagate" or require handling like exception, although there is a warning filter within the warnings module that lets you turn warnings into exceptions. Within the warnings module, there is a warn function. This is to warnings what raise is to exceptions. The warnings.warn function is what issues a warning. Listing 3.13 defines a function called oldFunction that we will assume is deprecated. To notify the user of this change, oldFunction issues a warning.

Listing 3.13 A Function that Warns of its Own Deprecation
# file "warn.py" def oldFunction(): import warnings warnings.warn('"oldFunction" is deprecated. Use "newFunction"') 


The Jython code in Listing 3.13 is the contents of a file called warn.py. Listing 3.14 uses this file as well. To test the warning in warn.py, start the Jython interpreter from the same directory that contains the file warn.py, and type:

>>> import warn >>> warn.oldFunction() /home/rbill/warn.py:3: UserWarning: "oldFunction" is deprecated. Use "new Function" warnings.warn('"oldFunction" is deprecated. Use "newFunction"') 


With warnings like this, the warning is displayed but the execution of the file would continue without interruption. Listing 3.13 provides one parameter to the warn function—the message to displayed. There is also an optional second parameter that is the type of warning (often called the warning category). This category should be a subclass of the exception class Warning, and there are four built-in choices for this optional "category" parameter. The generic category is, of course, Warning—the superclass of the other warnings. The others are UserWarning, SyntaxWarning, and RuntimeWarning. The default category when there is no category parameter supplied is UserWarning. There is also an optional third parameter to the warnings.warn function, which is stacklevel. The stacklevel is like a row of dominoes that all successively topple after the first one is pushed. To continue with this analogy, suppose the last domino to fall is the equivalent of the module that issues the warning— warn in our case. A stacklevel of 1 means the warning reports its occurrence inside of the module warn (the last domino). Maybe a different module actually called the deprecated oldFunction. This different module is like the second-to-last domino to fall. If you wanted the warning to report this second-to-last module, use a stacklevel of 2. A third-from-last module would be a stacklevel of 3. Remember that if no thirdfromlast object exists, the use of a stacklevel equal to 3 creates an error reported as KeyError: __name_ _. What if you want, or need, to disable warnings? The warnings module contains the warnings.filterwarning function that allows you to modify the behavior of warnings including disabling their display. Listing 3.14 demonstrates using the warnings.filterwarnings function to disable warning messages issued from the module warn.

Listing 3.14 Filtering a Warning
# file: warntest.py import warnings warnings.filterwarnings(action = 'ignore', module = 'warn') import warn warn.oldFunction() 


Running jython warntest. py creates no output. No warning occurs in Listing 3.14 because of the filter. There are actually six parameters for warnings.filter, (action, message, category, module, lineno, and append), but only the action parameter is required. The action parameter can be ignore, error, always, default, module, or once. The message parameter must be a string that matches the warning message you are trying to filter. The category parameter must be a class that is a subclass of warning. The module parameter limits the filter to warnings coming from that specific module, and it must be a string. There is also a lineno and append parameter. If provided, lineno must be a positive integer and restricts the filter to only warnings coming from that specific line. The append parameter designates whether the filter you are creating should be appended to the end of existing filters or inserted to front of the list of filters. The Python language description includes a -W command-line option for specifying certain behaviors for warnings. This is not implemented in Jython at the time of this writing, so check the Jython website or the website associated with this tutorial for more information on this.

Comparing Jython and Java

The subtle differences between Jython's and Java's error handling might be a source of confusion as you switch back and forth between languages. I often find myself confusing throw with raise, and catch with except. Beyond the obvious difference in statement names, Java allows a full try/catch/finally as in the following:

try {
 throw new IllegalAccessException("Illegal access message"); } catch (IllegalAccessException e) {
 e.print } finally {
 //Add some cleanup code here that is always executed } 


Jython only has the try/finally or try/except/else, and try/except/finally is a syntax error in Jython. The simple assertion facility supplied with Jython's assert statement is available in Java since version 1.4, but not in earlier versions of Java. The warnings framework is also unique to Jython. There isn't a comparable facility in Java for library developers to use in the deprecation and evolution of their libraries. Yes, there are documentation and compile-time errors, but nothing that supplies forewarning as the code evolves. This framework seemed to evolve as a means of enabling the evolution of Python itself—the core language, but it is valuable for any module and could be very useful especially for internal tools where changes can occur rapidly. Java also has compile-time checks to ensure that whatever could be thrown is either declared thrown or caught. If method A calls method B, and B throws an exception, A must catch it or also declare it as thrown. This requirement is absent from Jython.

Screenshot CONTENTS Screenshot
Comments