Chapter 9. Exception Handling

Exceptions are a solution to a difficult problem: How can programs deal with unexpected errors? For instance, what happens if a file disappears in the middle of being read because some other program on your computer has deleted it? Or what if the Web site your program is downloading pages from suddenly crashes?

In these and many other situations, what Python does is raise an exception. An exception is a special kind of error object that you can catch and then examine in order to determine how to handle the error.

Exceptions can change the flow of control of your program. Depending on when it occurs, an exception can cause the flow of control to jump out of the middle of a function or loop into another block of code that does error handling.

Often, you cannot be sure exactly which line might raise an exception, and this creates some tricky problems. Thus Python provides special exception-handling constructs for both catching exceptions and executing clean-up code whether or not an exception is raised.

Exceptions

An example of an exception is IOError, which is raised when you try to open a file that doesn’t exist:

>>> open('unicorn.dat')
Traceback (most recent call last):
  File "<pyshell#1>", line 1, in <module>
    open('unicorn.dat')
  File "C:Python30libio.py", line 284, in __new__
    return open(*args, **kwargs)
  File "C:Python30libio.py", line 223, in open
    closefd)
IOError: [Errno 2] No such file or directory: 'unicorn.dat'

When an exception is raised and is not caught or handled in any way, Python immediately halts the program and outputs a traceback, which is a list of the functions that were called before the exception occurred. This can be quite useful in pinning down exactly what line causes an error.

The last line of the traceback indicates that an IOError exception has been raised, and, specifically, it means that unicorn.dat could not be found in the current working directory. The error message given by an IOError differs depending on the exact reason for the exception.

Raising an exception

As we saw with the open function, Python’s built-in functions and library functions usually raise exceptions when something unexpected happens.

For instance, dividing by zero throws an exception:

>>> 1/0
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    1/0
ZeroDivisionError: int division or modulo by zero

Syntax errors can also cause exceptions in Python:

>>> x := 5
SyntaxError: invalid syntax (<pyshell#2>, line 1)
>>> print('hello world)

SyntaxError: EOL while scanning string literal (<pyshell#3>, line 1)

You can also intentionally raise an exception anywhere in your code using the raise statement. For example:

>>> raise IOError('This is a test!')
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    raise IOError('This is a test!')
IOError: This is a test!

Python has numerous built-in exceptions organized into a hierarchy: See the Python documentation (http://docs.python.org/dev/3.0/library/exceptions.html#bltin-exceptions) for more details.

Catching Exceptions

You have essentially two options for dealing with a raised exception:

  1. Ignore the exception and let your program crash with a traceback. This is usually what you want when you are developing a program, since the traceback provides helpful debugging information.

  2. Catch the exception and print a friendly error message, or possibly even try to fix the problem. This is almost always what you want to do with a program meant to be used by non-programmers: Regular users don’t want to deal with tracebacks!

Here’s an example of how to catch an exception. Suppose you want to read an integer from the user, prompting repeatedly until a valid integer is entered:

def get_age():
    while True:
        try:
            n = int(input('How old are you? '))
            return n
        except ValueError:
            print('Please enter an integer value.')

Inside this function’s while-loop is a try/except block. You put whatever code you like in the try part of the block, with the understanding that one or more lines of that code might raise an exception.

If any line of the try block does raise an exception, then the flow of control immediately jumps to the except block, skipping over any statements that have not been executed yet. In this example, the return statement will be skipped when an exception is raised.

If the try block raises no exceptions, then the except ValueError block is ignored and not executed.

So in this example, the int() function raises a ValueError if the user enters a string that is not a valid integer. When that happens, the flow of control jumps to the except ValueError block and prints the error message. When a ValueError is raised, the return statement is skipped—the flow of control jumps immediately to the except block.

If the user enters a valid integer, then no exception is raised, and Python proceeds to the following return statement, thus ending the function.

Try/except blocks

Try/except blocks work a little bit like if-statements. However, they are different in an important way: If-statements decide what to do based on the evaluation of Boolean expressions, while try/except blocks decide what to do based on whether or not an exception is raised.

A function can raise more than one kind of exception, and it can even raise the same type of exception for different reasons. Look at these three different int() exceptions (the tracebacks have been trimmed for readability):

>>> int('two')
ValueError: invalid literal for int() with base 10: 'two'
>>> int(2, 10)
TypeError: int() can't convert non-string with explicit base
>>> int('2', 1)
ValueError: int() arg 2 must be >= 2 and <= 36

So int() raises ValueError for at least two different reasons, while it raises TypeError in at least one other case.

Catching multiple exceptions

You can write try/except blocks to handle multiple exceptions. For example, you can group together multiple exceptions in the except clause:

def convert_to_int1(s, base):
    try:
        return int(s, base)
    except (ValueError, TypeError):
        return 'error'

Or, if you care about the specific exception that is thrown, you can add extra except clauses:

def convert_to_int2(s, base):
    try:
        return int(s, base)
    except ValueError:
        return 'value error'
    except TypeError:
        return 'type error'

Catching any exception

If you write an except clause without any exception name, it will catch any and all exceptions:

def convert_to_int3(s, base):
    try:
        return int(s, base)
    except:
        return 'error'

This form of except clause will catch any exception—it doesn’t care about what kind of error has occurred, just that one has occurred. In many situations, this is all you need.

Clean-Up Actions

A finally code block can be added to any try/except block to perform clean-up actions. For example:

def invert(x):
    try:
        return 1 / x
    except ZeroDivisionError:
        return 'error'
    finally:
        print('invert(%s) done' % x)

The code block underneath finally will always be executed after the try block or the except block. This is quite useful when you have code that you want to perform regardless of whether an exception is raised. For instance, file close statements are often put in finally clauses so that files are guaranteed to be closed, even if an unexpected IOError occurs.

The with statement

Python’s with statement is another way to ensure that clean-up actions (such as closing a file) are done as soon as possible, even if there is an exception. For example, consider this code, which prints a file to the screen with numbers for each line:

num = 1
f = open(fname)
for line in f:
    print('%04d %s' % (num, line),
          end = '')
    num = num + 1
   # following code

What’s unknown here is when the file object f is closed. At some point after the for-loop, f will usually be closed. But we don’t know when precisely that will happen: It will remain unclosed but unused for an indeterminate amount of time, which might be a problem if other programs try to access the file.

To ensure that the file is closed as soon as it is no longer needed, use a with statement:

num = 1
  with open(fname, 'r') as f:
     for line in f:
          print('%04d %s' % (num, line), end = '')
          num = num + 1

The onscreen results are the same as the previous code, but when you use a with statement, the file objects’ clean-up action (that is to say, closing the file) is automatically called as soon as the for-loop ends. Thus f does not sit around unclosed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.223.190