官术网_书友最值得收藏!

Understanding exceptions

The word exception is loaded. The definition seems clear: exceptions are exceptional. I'll say, in Python at least, this definition is simply not true. A normal Python program may handle and raise any number of exceptions as it hums along quite nicely.

Consider the Pythonic idiom we have already pointed out multiple times: Easier to Ask Forgiveness than Permission. It is usually expressed as follows:

try:
    spam.eggs()
except AttributeError:
    spam.ham()

The preceding code is preferred over the following code, which uses a Look Before You Leap style:

if hasattr(spam, 'eggs'):
    spam.eggs()
else:
    spam.ham()

There is nothing exceptional about exceptions in the first example. Describing them as exceptional is more accurate when describing how they behave rather than how they are used.

I prefer the following definition:

"Exceptions are a form of (exceptional) flow control."

To illustrate that definition, consider the following lines of code:

if spam:
    for i in xrange(count):
        eggs(i)

We can observe in the preceding code that there are two instances of explicit flow control: the if statement and the for loop can change how the function executes. There is a hidden flow control, however, and it can occur nearly anywhere. For example, the xrange or eggs function can raise an exception for any number reasons. An exception can also be raised almost anywhere if the operating system sends a signal to Python to terminate the process. In either case, we experience an interruption that isn't handled by the code we are reading. It is a hidden and implicit flow control. This type of flow control is exceptional and it is handled through exceptions.

A corollary of exceptions being flow control is that exceptions are not always conceptual errors. When I use the term error, such as in this chapter's introductory paragraph, I mean it is as a problem the programmer did not expect or account for. Exceptions are merely the mechanism by which errors are usually indicated.

We don't need to be scared of exceptions any more than we should be scared of for loops. We just need to know when to raise an exception and how to handle an exception. Such will be the focus of this chapter.

Introducing exception types

An exception is just like any other object in Python. You can create, inspect, and see their class hierarchy as follows:

>>> ex = SystemError('a', 1)
>>> [t.__name__ for t in type(ex).__mro__]
['SystemError', 'StandardError', 'Exception', 'BaseException', 'object']
>>> ex.args
('a', 1)
>>> dir(ex)
['__class__', '__delattr__', ...'args', 'message']

One important thing to note here is that you can think of all exceptions as inheriting from the Exception base class. There is a history and rationale behind BaseException and StandardError, but you should treat Exception as the base class and ignore the latter two. If you would like more information, you can read about Python's built-in exceptions at http://docs.python.org/2/library/exceptions.html.

Explaining try/catch/finally flow control

Python's flow control for exceptions is nuanced. However, it is also an integral part of the language, so it is vital to understand. This section will serve as a quick primer or refresher. If you are already comfortable with exception handling, feel free to skip to the next section. If you have never encountered Python exception handling before, I suggest referring to a tutorial before proceeding. Two options are the official Python tutorial on errors at https://docs.python.org/2/tutorial/errors.html and the relevant chapter in the free Dive Into Python e-book at http://www.diveintopython.net/file_handling/index.html#fileinfo.exception.

The simplest form of exception handling is the basic try/except statement, demonstrated by the following code:

>>> try:
...     1 + 'a'
... except:
...     print 'Errored!'
Errored!

The naked except keyword should not be used because it will catch every exception, including things such as SystemExit which should not usually be caught. Instead, we should explicitly declare what exception types we want to catch. The change to the previous example is highlighted.

>>> try:
...     1 + 'a'
... except TypeError:
...     print 'Errored!'
Errored!

Instead of a single type after the except keyword, we can use a tuple to catch multiple types of exceptions with the same except clause. The change to the previous example is highlighted.:

>>> try:
...     1 + 'a'
... except (TypeError, RuntimeError):
...     print 'Errored!'
Errored!

We can handle different types of exceptions with unique clauses by stacking except statements. The first except expression that matches the exception type will be used.

>>> try:
...     1 + 'a'
... except RuntimeError:
...     print 'RuntimeError!'
... except TypeError:
...     print 'TypeError!'
TypeError!

We can also assign the caught exception to a variable. This allows us to use it from within the except clause, as demonstrated in the following code.

>>> try:
...     1 + 'a'
... except TypeError as ex:
...     print repr(ex)
TypeError("unsupported operand type(s) for +: 'int' and 'str'",)

We can reraise the original exception using a naked raise statement. This allows us to run custom error handling code without affecting the exception. The following code prints when the error is caught, and then raises the original error, which is caught by the interpreter and printed.

>>> try:
...     1 + 'a'
... except TypeError:
...     print 'Errored!'
...     raise
Errored!
Traceback (most recent call last):
TypeError: unsupported operand type(s) for +: 'int' and 'str'

You can also create an exception instance and raise that, as in the following code:

>>> try:
...     1 + 'a'
... except TypeError:
...     raise KeyError('hi!')
Traceback (most recent call last):
KeyError: 'hi!'

Use the finally keyword to provide cleanup that happens after the try/except code has been run. The code in the finally clause is executed "on the way out" when any other clause of the try/except statement is completed. Practically, this means the finally code is run whether or not an exception is raised, and it is the last clause to run.

>>> try:
...     1 + 'a'
... except TypeError:
...     print 'Errored!'
... finally:
... print 'Cleaned up.'
Errored!
Cleaned up.

Python also provides an else clause that will be run if the try does not raise an error. No example will be shown since we do not use try/except/else in this book.

If any of this is still unclear, refer to one of the tutorials listed at the beginning of this section. It is very important to have some familiarity with these aspects of exception flow control because the rest of this chapter relies on it heavily.

Explaining traceback objects

A Python traceback (called a stack trace in many languages) contains the information about where an error occurred. You've probably seen a traceback similar to the one we provoke here:

>>> 1 + 'a'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

The traceback records the line that raised an exception, the top-level caller, and all the calls in between the two. As we'll see later, the traceback usually also contains the contents of each line. It did not in the preceding example because our code is created from within the interactive prompt.

When a traceback is formatted into a string, the last line is the exception's representation. This is usually the exception's type name and formatted arguments.

There are many more nuances to dealing with tracebacks that we will go over as we need use them in more depth.

Explaining the exc_info tuple

The type of an exception, the exception instance, and the exception's traceback are together known as the exc_info tuple or exception info. It is known by this name because the information about the exception currently being handled is accessible by calling the sys.exc_info() function. In the following example, notice how we assign the exception info to the si variable, and can then access the exception information from outside of the except clause.

>>> import sys
>>> try:
...     1 + 'a'
... except TypeError:
...     si = sys.exc_info()
>>> print si[0]
<type 'exceptions.TypeError'>
>>> print repr(si[1])
TypeError("unsupported operand type(s) for +: 'int' and 'str'",)
>>> print si[2]
<traceback object at 0x0...>

We can see from this example that the exc_info tuple is an important part of working with exceptions in Python. It contains all the necessary information for an exception: the exception instance, type, and traceback.

You should be careful when capturing or using the sys.exc_info() tuple. Like most things with exceptions, there are several gotchas, such as creating circular references or garbage collection problems. We'll ignore them for this chapter since they aren't common, but keep them in mind as you go further with Python. While the exception and traceback concept may be straightforward, working with the exc_info tuple, traceback objects, and the raise statement have some rough edges.

I don't find Python 2's exception design particularly elegant or intuitive, but the good news is Python 3 has improved in this area. Despite these complaints, Python's exception handling works well enough once you learn the essential syntax and the lessons taught here.

主站蜘蛛池模板: 沂源县| 岑溪市| 东台市| 玉溪市| 敖汉旗| 淳化县| 巴东县| 阿拉尔市| 同江市| 亚东县| 宁河县| 康马县| 石林| 融水| 泰顺县| 阜阳市| 镇坪县| 五峰| 安平县| 石狮市| 辽阳市| 长治市| 筠连县| 武汉市| 上杭县| 茂名市| 静海县| 竹山县| 维西| 峨山| 晋江市| 山阳县| 扶风县| 台前县| 武义县| 平武县| 惠安县| 绍兴市| 陆良县| 平远县| 郯城县|