- Practical Maya Programming with Python
- Robert Galanakis
- 1484字
- 2021-09-03 10:05:27
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.
- Beginning Java Data Structures and Algorithms
- Spring Boot+Spring Cloud+Vue+Element項目實戰:手把手教你開發權限管理系統
- Java程序設計與計算思維
- Instant QlikView 11 Application Development
- 青少年Python編程入門
- 移動界面(Web/App)Photoshop UI設計十全大補
- Active Directory with PowerShell
- 深入淺出React和Redux
- 微服務從小白到專家:Spring Cloud和Kubernetes實戰
- 持續輕量級Java EE開發:編寫可測試的代碼
- Web App Testing Using Knockout.JS
- ASP.NET求職寶典
- 零基礎C#學習筆記
- Learning C++ by Creating Games with UE4
- 大話代碼架構:項目實戰版