what is exception in python
Up until now this book has carefully avoided the problems of trapping errors. This is because we’ve concentrated on the fundamentals of the language such trapping an error. This chapter will use Perl as an example language because most people have probably been exposed to Perl as a scripting language. Consider, for example, the following Perl code for opening a file:
open (FILE, "file.txt") || die "Couldn't open file.txt: $! ";
The way the error is trapped is that the interpreter looks for a return value from the function; the return value will be a positive integer, representing a true value if the function call works. If the open function call fails, the die function kills the Perl interpreter (or the enclosing eval block) and reports the error, using the string obtained from the $! special variable.
There are a few problems with this method of error trapping:
•You can only detect success or failure – there’s no in-between.
•If you want to detect the reason for the failure, you have to examine the $! variable.
•You can only really handle the failure – not the reason for the failure – unless you embed every call into an eval block.
This is a limitation not only of Perl, but also of C/C++, which uses a similar method of error checking, although the C headers normally include the specific error numbers which you can trap, but only within multiple if or switch statements. C++ supports an exception system, but it is underused because of historical problems with the compilers that support exceptions. Pascal, Visual Basic (VB), and most other languages also follow the same basic methodology-they check for a true/false return status. Now let’s look at a different, but also common situation when you might want to trap an error:
$den = 25;
$num= 0;
print $den/$num, "\n";
In Perl, this code causes a fatal error (the interpreter terminates) at run time because division by zero is undefined. It’s impossible to trap this error within the normal execution of Perl unless you either check the values to make sure you aren’t dividing by zero or you embed the statement into an eval block, which is essentially the same as embedding a Perl interpreter into the Perl interpreter. The problem here is that you can’t always embed everything into an eval block, and checking every value before you do a calculation is excellent programming practice, but excessive if you want an optimized script.
There are no other ways to trap the execution of individual statements at execution time either in Perl, C, or C++, Python, however, provides a different method. When an error occurs within Python, an exception is raised and you can use built-in statements to raise your own exceptions and to trap the exceptions and handle them safely.
What Is an Exception?
An exception is a special type of Python object that can be used to pass information about a failure from the called module to the caller. When an exception is raised, it can be trapped, and the exception information is passed to an exception handler. Unlike Perl and C. you can get a precise description of the problem and even additional information about the error that occurred.
The best way to think about an exception is as a configurable and expandable signal handler (see “Exceptions and goto”). You create an exception handler using the try statement and then execute the statements that you want to monitor. As soon as an exception occurs, control returns to the try statement. You don’t have to monitor each function call, and you don’t have to rely on simple return values to determine whether a block of code executed successfully, you just need to be able to handle the exception if it occurs.
Exceptions and goto
You might think that Python’s exceptions sound a bit like the C, VB, and Perl goto functions in their various guises. Or you might think that exceptions provide a similar functionality to the C setjmp()/longimp(). However, in all these cases there are some limitations. Any system based on the premise of a goto statement is open to abuse, even with the advanced status recording offered by the setjmp() functions.
The problem with a goto statement is that you jump from the current location to the new location without clearing up loose ends-what happens with files and variables that had been opened within the context of the code block you were in? It’s also not possible. without global variables, to pass any useful information during the jump. Any form of goto is considered bad programming practice because it breaks the normal flow and the safe execution of a program.
The Python exception-handling system is much more advanced. Exceptions are just like return statements used in functions, except that an exception can be raised anywhere in the caller and handled by a caller further up the chain, irrespective of where the exception handler is located or where the exception was raised. Because exceptions are a built-in feature of the language, the process is much cleaner than other methods – you don’t have to rely on signals or other features. Exceptions are also objects and because they are objects, they can have values and methods assigned to.
them. When you raise an exception, you are returning an object, and that means you can actually use exceptions for more than just error checking. Other uses for exceptions include:
•Multiple error handling: You don’t need to check individual return values and compare them against a known list. When you call a function or execute a block, you only need to know how to handle specific exceptions; any other errors can either be ignored or trapped using a generic exception handler.
•Special control flow: By using exception handlers, you can alter the flow of an application based on the capability to perform a specific action. Imagine, for example, a program that either opens a network connection or uses a local file instead. Using an exception handler, you can switch to the default file if the network connection fails.
•Event monitoring: You can use the exception system to return the results from an event without having to use separate global variables and without having to check the return value explicitly. The normal way within Perl or C to handle an event is to check the return value; to verify whether any information has been returned, you check the number of items returned in a list or hash. With C. you have to use an integer return value and a global value, or supply a pointer argument to the function for the information to be written to.
Exceptions are based on objects or object classes, and there is a certain level of inheritance between individual exceptions. All exceptions are members of a base Exception class and then the hierarchy continues, inheriting individual features of each class until you reach a specific exception. For example, the ArithmeticError exception handles errors in calculations. More specifically, the ZeroDivision Error exception is designed to identify calculations like the earlier example where division by zero was attempted.
Using objects and classes, you can build exception handlers based on generic errors or on very specific errors. For example, if you are calculating using data from an unknown source, you might want to trap just a basic arithmetic error, when using internal data, you’ll want to know what sort of calculation error was raised.
What Happens When an Exception Occurs?
When an exception occurs, the default operation is for the Python interpreter to identify the error and then produce a stack trace about where the error occurred and how you got there. This is more useful than the typical error output produced by Perl or C where you normally see only the line where an error has occurred. This can make tracing the reason for the error very difficult. To demonstrate the process, here’s a simple script that tries to divide a number by zero:
print 24/0
Not surprisingly this raises an exception, since you cannot divide by zero:
Traceback (innermost last):
File "t.py", line 1, in ?
print 24/0
ZeroDivisionError: integer division or modulo
If you embed this calculation into a function call, you get a more detailed description of how the error occurred:
def divide (x,y):
return x/y
def calc(x,y):
return x* (divide (x, y))
print calc(24,0)
If you execute this script, you see a detailed description of where the error occurred, over and above the simple line number:
Traceback (innermost last):
File "t.py", line 7, in ?
print calc(24.0)
File "t.py", line 5, in cale
return x*(divide (x,y))
File "t.py", line 2, in divide
return x/y
ZeroDivisionError: integer division or modulo
You can see in the preceding example how the report is given in the form of a stack trace; you can backtrack from the actual error, the calculation in line 2, up to the original call that triggered the error, the call in line 7, which in turn, made a call through line 5. Note also that the stack trace includes the function name and the name of the source file in which the error occurred.
The trick is to try and trap these errors so that you can give the user a better idea of what error occurred. To do this, you embed the call into an exception handler made up of a try statement and the code to handle any errors. Let’s rewrite the division script and include an exception handler:
def divide(x,y):
return x/y
def calc(x,y):
return x*(divide (x,y))
try:
print calc(24,0)
except:
print "Whoa!: Those numbers don't seem to work!"
Here you’ve created a simple exception handler that traps every type of exception and reports an error. Because you haven’t specified which exception should be trapped, this script also reports an error if there is a syntax problem or if you supplied a string to the calc function. Python actually defines a number of different exceptions and exception classes to handle specific error types; we’ll be looking at those in more detail later in this chapter.
The Python exception system works differently than most other languages. Normally in a situation like the preceding script, you’d check the values before trying the calculation to make sure that the information was correct and didn’t raise an error during execution. For example, in Perl you’d do something like this:
print calc(24,0);
sub divide
{
my ($x, $y) = @_ ;
die "Cannot divide by zero with $y" if ($y == 0);
$x/Sy;
}
sub calc
{
my (Sx, Sy) =@__;
$x* (divide ($x, $y));
}
The problem here is that you end up using lots of code to trap the different types of errors. What if you called the calc function with a string instead of a number? If you call calc(‘Hello’,1) in Perl, the return value is 0 because Perl tries to convert the string into a number and fails, and therefore makes the assumption that the value of the string is 0. What you really want is for the invalid argument to be identified as an error. To do that, you’d need to insert yet another if statement to test the contents of the arguments and determine the type.
Let’s try it in Python and report an error to the user if there is a problem with the numbers you are trying to use. The following example takes advantage of the Python’s ability to identify specific error types:
def divide(x,y):
return x/y
def calc(x,y):
return x*(divide(x,y))
try:
print calc("Hello",1)
except ZeroDivisionError:
print "Whoa!: Your trying to divide by zero and you can't!"
except TypeError:
print "Whoat: That doesn't look like a number: "
except:
print "Whoa!: Some other kind of error occurred!"
If you run this script, you’ll get the following output:
Whoa!: That doesn't look like a number!
Now you’re identifying division by zero and invalid type errors, but you’re still not modifying the functions, nor do you have to predict what the possible errors might be. Instead, you’re handling errors that occur when calling the function, and then catching any possible exceptions that might occur. You can be as specific or as vague as you like: in the preceding example, you’ve got both specific (ZeroDivisionError and TypeError) and a generalized Exception error. The script doesn’t care how the statement fails; we can trap it and act upon it.
[…] Exceptions and Error Trapping […]