In the prior chapter we took a quick look at exception-related statements in action. Here, we’re going
to dig a bit deeper—this chapter provides a more formal introduction to
exception processing syntax in Python. Specifically, we’ll explore the
details behind the try
, raise
, assert
, and with
statements. As we’ll see, although these
statements are mostly straightforward, they offer powerful tools for
dealing with exceptions in Python code.
One procedural note up front: The exception story has changed in
major ways in recent years. As of Python 2.5, the finally
clause can appear in the same
try
statement as except
and else
clauses (previously, they could not be
combined). Also, as of Python 3.0 and 2.6, the new with
context manager statement has become
official, and user-defined exceptions must now be coded as class
instances, which should inherit from a built-in exception superclass.
Moreover, 3.0 sports slightly modified syntax for the raise
statement and except
clauses. I will focus on the state of
exceptions in Python 2.6 and 3.0 in this edition, but because you are
still very likely to see the original techniques in code for some time
to come, along the way I’ll point out how things have evolved in this
domain.
Now that we’ve seen the basics, it’s time for the details.
In the following discussion, I’ll first present try
/except
/else
and try
/finally
as separate statements, because in
versions of Python prior to 2.5 they serve distinct roles and cannot
be combined. As mentioned in the preceding note, in Python 2.5 and
later except
and finally
can be mixed in a single try
statement; I’ll explain the implications
of this change after we’ve explored the two original forms in
isolation.
The try
is a compound
statement; its most complete form is sketched below. It starts with a
try
header line, followed by a
block of (usually) indented statements, then one or more except
clauses that identify exceptions to
be caught, and an optional else
clause at the end. The words try
,
except
, and else
are associated by indenting them to the
same level (i.e., lining them up vertically). For reference, here’s
the general format in Python 3.0:
try: <statements
> # Run this main action first except <name1
>: <statements
> # Run ifname1
is raised during try block except (name2
,name3
): <statements
> # Run if any of these exceptions occur except <name4
> as <data
>: <statements
> # Run ifname4
is raised, and get instance raised except: <statements
> # Run for all (other) exceptions raised else: <statements
> # Run if no exception was raised during try block
In this statement, the block under the try
header represents the main
action of the statement—the code you’re trying to run. The
except
clauses define
handlers for exceptions raised during the
try
block, and the else
clause (if coded) provides a handler to
be run if no exceptions occur. The <
data
>
entry here has to do with a feature of
raise
statements and exception
classes, which we will discuss later in this chapter.
Here’s how try
statements
work. When a try
statement is
entered, Python marks the current program context so it can return to
it if an exception occurs. The statements nested under the try
header are run first. What happens next
depends on whether exceptions are raised while the try
block’s statements are running:
If an exception does occur while the
try
block’s statements are
running, Python jumps back to the try
and runs the statements under the
first except
clause that
matches the raised exception. Control resumes below the entire
try
statement after the
except
block runs (unless the
except
block raises another
exception).
If an exception happens in the try
block and no
except
clause matches, the
exception is propagated up to the last matching try
statement that was entered in the
program or, if it’s the first such statement, to the top level of
the process (in which case Python kills the program and prints a
default error message).
If no exception occurs while the statements under the
try
header run, Python runs the
statements under the else
line
(if present), and control then resumes below the entire try
statement.
In other words, except
clauses catch any exceptions that happen while the try
block is running, and the else
clause runs only if no exceptions
happen while the try
block
runs.
except
clauses are
focused exception handlers—they catch exceptions
that occur only within the statements in the associated try
block. However, as the try
block’s statements can call functions
coded elsewhere in a program, the source of an exception may be
outside the try
statement itself.
I’ll have more to say about this when we explore try
nesting in Chapter 35.
When you write a try
statement, a variety of clauses can appear after the try
header. Table 33-1 summarizes all the possible
forms—you must use at least one. We’ve already met some of these: as
you know, except
clauses catch
exceptions, finally
clauses run
on the way out, and else
clauses
run if no exceptions are encountered.
Syntactically, there may be any number of except
clauses, but
you can code else
only if there is at least one except
, and there can be only one else
and one finally
. Through Python 2.4, the finally
clause must appear alone (without
else
or except
); the try
/finally
is really a different statement.
As of Python 2.5, however, a finally
can appear in the same statement as except
and else
(more on the ordering rules later in
this chapter when we meet the unified try
statement).
Clause form | Interpretation |
| Catch all (or all other) exception types. |
| Catch a specific exception only. |
| Catch the listed exception and its instance. |
| Catch any of the listed exceptions. |
| Catch any listed exception and its instance. |
| Run if no exceptions are raised. |
| Always perform this block. |
We’ll explore the entries with the extra as
value
part
when we meet the raise
statement.
They provide access to the objects that are raised as
exceptions.
The first and fourth entries in Table 33-1 are new here:
except
clauses that
list no exception name (except:
) catch
all exceptions not previously listed in the
try
statement.
except
clauses that
list a set of exceptions in parentheses (except (e1, e2, e3):
) catch
any of the listed exceptions.
Because Python looks for a match within a given try
by inspecting the except
clauses from top to bottom, the
parenthesized version has the same effect as listing each exception
in its own except
clause, but you
have to code the statement body only once. Here’s an example of
multiple except
clauses at work,
which demonstrates just how specific your handlers can be:
try: action() except NameError: ... except IndexError: ... except KeyError: ... except (AttributeError, TypeError, SyntaxError): ... else: ...
In this example, if an exception is raised while the call to
the action
function is running,
Python returns to the try
and
searches for the first except
that names the exception raised. It inspects the except
clauses from top to bottom and left
to right, and runs the statements under the first one that matches.
If none match, the exception is propagated past this try
. Note that the else
runs only when
no exception occurs in action
—it does not run when an exception
without a matching except
is
raised.
If you really want a general “catch-all” clause, an empty
except
does the trick:
try: action() except NameError: ... # Handle NameError except IndexError: ... # Handle IndexError except: ... # Handle all other exceptions else: ... # Handle the no-exception case
The empty except
clause is a sort of
wildcard feature—because it catches everything,
it allows your handlers to be as general or specific as you like. In
some scenarios, this form may be more convenient than listing all
possible exceptions in a try
. For
example, the following catches everything without listing
anything:
try:
action()
except:
... # Catch all possible exceptions
Empty except
s also raise
some design issues, though. Although convenient, they may catch
unexpected system exceptions unrelated to your code, and they may
inadvertently intercept exceptions meant for another handler. For
example, even system exit calls in Python trigger exceptions, and
you usually want these to pass. That said, this structure may also
catch genuine programming mistakes for you which you probably want
to see an error message. We’ll revisit this as a gotcha at the end
of this part of the book. For now, I’ll just say “use with
care.”
Python 3.0 introduced an alternative that solves one of these
problems—catching an exception named Exception
has almost the same effect as an
empty except
, but ignores
exceptions related to system exits:
try:
action()
except Exception:
... # Catch all possible exceptions, except exits
This has most of the same convenience of the empty
except, but also most of the same
dangers. We’ll explore how this form works its voodoo in the next
chapter, when we study exception classes.
Version skew note: Python 3.0 requires
the except E as V:
handler
clause form listed in Table 33-1 and used in this book,
rather than the older except E,
V:
form. The latter form is still available (but not
recommended) in Python 2.6:
if used, it’s converted to the former. The change was made to
eliminate errors that occur when confusing the older form with two
alternate exceptions, properly coded in 2.6 as except (E1, E2):
. Because 3.0 supports
the as
form only, commas in a
handler clause are always taken to mean a tuple, regardless of
whether parentheses are used or not, and the values are
interpreted as alternative exceptions to be caught. This change
also modifies the scoping rules: with the new as
syntax, the variable V
is deleted at the end of the except
block.
The purpose of the else
clause is not always immediately obvious to Python newcomers.
Without it, though, there is no way to tell (without setting and
checking Boolean flags) whether the flow of control has proceeded
past a try
statement because no
exception was raised, or because an exception occurred and was
handled:
try:...run code...
except IndexError:...handle exception...
# Did we get here because the try failed or not?
Much like the way else
clauses in loops make the exit cause more apparent, the else
clause provides syntax in a try
that makes what has happened obvious
and unambiguous:
try:...run code...
except IndexError:...handle exception...
else:...no exception occurred...
You can almost emulate an else
clause by moving its code into the
try
block:
try:...run code...
...no exception occurred...
except IndexError:...handle exception...
This can lead to incorrect exception classifications, though.
If the “no exception occurred” action triggers an IndexError
, it will register as a failure
of the try
block and erroneously
trigger the exception handler below the try
(subtle, but true!). By using an
explicit else
clause instead, you
make the logic more obvious and guarantee that except
handlers will run only for real
failures in the code you’re wrapping in a try
, not for failures in the else
case’s action.
Because the control flow through a program is easier to capture in
Python than in English, let’s run some examples that further
illustrate exception basics. I’ve mentioned that exceptions not
caught by try
statements
percolate up to the top level of the Python process and run Python’s
default exception-handling logic (i.e., Python terminates the
running program and prints a standard error message). Let’s look at
an example. Running the following module file, bad.py, generates a divide-by-zero
exception:
def gobad(x, y): return x / y def gosouth(x): print(gobad(x, 0)) gosouth(1)
Because the program ignores the exception it triggers, Python kills the program and prints a message:
% python bad.py
Traceback (most recent call last):
File "bad.py", line 7, in <module>
gosouth(1)
File "bad.py", line 5, in gosouth
print(gobad(x, 0))
File "bad.py", line 2, in gobad
return x / y
ZeroDivisionError: int division or modulo by zero
I ran this in a shell window with Python 3.0. The message
consists of a stack trace (“Traceback”) and the name of and details
about the exception that was raised. The stack trace lists all lines
active when the exception occurred, from oldest to newest. Note that
because we’re not working at the interactive prompt, in this case
the file and line number information is more useful. For example,
here we can see that the bad divide happens at the last entry in the
trace—line 2 of the file bad.py, a return
statement.[74]
Because Python detects and reports all errors at runtime by
raising exceptions, exceptions are intimately bound up with the
ideas of error handling and debugging in general. If you’ve worked
through this book’s examples, you’ve undoubtedly seen an exception
or two along the way—even typos usually generate a SyntaxError
or other exception when a file
is imported or executed (that’s when the compiler is run). By
default, you get a useful error display like the one just shown,
which helps you track down the problem.
Often, this standard error message is all you need to resolve
problems in your code. For more heavy-duty debugging jobs, you can
catch exceptions with try
statements, or use one of the debugging tools that I introduced in
Chapter 3 and will summarize again in
Chapter 35 (such as the pdb
standard library module).
Python’s default exception handling is often exactly what you want—especially for code in a top-level script file, an error generally should terminate your program immediately. For many programs, there is no need to be more specific about errors in your code.
Sometimes, though, you’ll want to catch errors and recover
from them instead. If you don’t want your program terminated when
Python raises an exception, simply catch it by wrapping the program
logic in a try
. This is an
important capability for programs such as network servers, which
must keep running persistently. For example, the following code
catches and recovers from the TypeError
Python raises immediately when
you try to concatenate a list and a string (the +
operator expects the same sequence type
on both sides):
def kaboom(x, y): print(x + y) # Trigger TypeError try: kaboom([0,1,2], "spam") except TypeError: # Catch and recover here print('Hello world!') print('resuming here') # Continue here if exception or not
When the exception occurs in the function kaboom
, control jumps to the try
statement’s except
clause, which prints a message.
Since an exception is “dead” after it’s been caught like this, the
program continues executing below the try
rather than being terminated by
Python. In effect, the code processes and clears the error, and your
script recovers:
% python kaboom.py
Hello world!
resuming here
Notice that once you’ve caught an error, control resumes at
the place where you caught it (i.e., after the try
); there is no direct way to go back to
the place where the exception occurred (here, in the function
kaboom
). In a sense, this makes
exceptions more like simple jumps than function calls—there is no
way to return to the code that triggered the error.
The other flavor of the try
statement is a specialization that has
to do with finalization actions. If a finally
clause is included in a try
, Python will always run its block of
statements “on the way out” of the try
statement, whether an exception occurred
while the try
block was running or
not. Its general form is:
try: <statements
> # Run this action first finally: <statements
> # Always run this code on the way out
With this variant, Python begins by running the statement block
associated with the try
header
line. What happens next depends on whether an exception occurs during
the try
block:
If no exception occurs while the try
block is running, Python jumps back
to run the finally
block and
then continues execution past below the try
statement.
If an exception does occur during the
try
block’s run, Python still
comes back and runs the finally
block, but it then propagates the exception up to a higher
try
or the top-level default
handler; the program does not resume execution below the try
statement. That is, the finally
block is run even if an
exception is raised, but unlike an except
, the finally
does not terminate the
exception—it continues being raised after the finally
block runs.
The try
/finally
form is useful when you want to be
completely sure that an action will happen after some code runs,
regardless of the exception behavior of the program. In practice, it
allows you to specify cleanup actions that always must occur, such as
file closes and server disconnects.
Note that the finally
clause
cannot be used in the same try
statement as except
and else
in Python 2.4 and earlier, so the
try
/finally
is best thought of as a distinct
statement form if you are using an older release. In Python 2.5, and
later, however, finally
can appear
in the same statement as except
and
else
, so today there is really a
single try
statement with many
optional clauses (more about this shortly). Whichever version you use,
though, the finally
clause still
serves the same purpose—to specify “cleanup” actions that must always
be run, regardless of any exceptions.
As we’ll also see later in this chapter, in Python 2.6 and
3.0, the new with
statement and its context managers
provide an object-based way to do similar work for exit actions.
Unlike finally
, this new
statement also supports entry actions, but it is limited in scope to
objects that implement the context manager protocol.
We saw some simple try
/finally
examples in the prior chapter.
Here’s a more realistic example that illustrates a typical role for
this statement:
class MyError(Exception): pass def stuff(file): raise MyError() file = open('data', 'w') # Open an output file try: stuff(file) # Raises exception finally: file.close() # Always close file to flush output buffers print('not reached') # Continue here only if no exception
In this code, we’ve wrapped a call to a file-processing
function in a try
with a finally
clause to make sure that the file
is always closed, and thus finalized, whether the function triggers
an exception or not. This way, later code can be sure that the
file’s output buffer’s content has been flushed from memory to disk.
A similar code structure can guarantee that server connections are
closed, and so on.
As we learned in Chapter 9, file objects
are automatically closed on garbage collection; this is especially
useful for temporary files that we don’t assign to variables.
However, it’s not always easy to predict when garbage collection
will occur, especially in larger programs. The try
statement makes file closes more
explicit and predictable and pertains to a specific block of code.
It ensures that the file will be closed on block exit, regardless of
whether an exception occurs or not.
This particular example’s function isn’t all that useful (it
just raises an exception), but wrapping calls in try
/finally
statements is a good way to ensure
that your closing-time (i.e., termination) activities always run.
Again, Python always runs the code in your finally
blocks, regardless of whether an
exception happens in the try
block.[75]
When the function here raises its exception, the control flow
jumps back and runs the finally
block to close the file. The exception is then propagated on to
either another try
or the default
top-level handler, which prints the standard error message and shuts
down the program; the statement after this try
is never reached. If the function here
did not raise an exception, the program would
still execute the finally
block
to close the file, but it would then continue below the entire
try
statement.
Notice that the user-defined exception here is again defined with a class—as we’ll see in the next chapter, exceptions today must all be class instances in both 2.6 and 3.0.
In all versions of Python prior to Release 2.5 (for its first
15 years of life, more or less), the try
statement came in two flavors and was
really two separate statements—we could either use a finally
to ensure that cleanup code was
always run, or write except
blocks
to catch and recover from specific exceptions and optionally specify
an else
clause to be run if no
exceptions occurred.
That is, the finally
clause
could not be mixed with except
and
else
. This was partly because of
implementation issues, and partly because the meaning of mixing the
two seemed obscure—catching and recovering from exceptions seemed a
disjoint concept from performing cleanup actions.
In Python 2.5 and later, though (including 2.6 and 3.0, the
versions used in this book), the two statements have merged. Today, we
can mix finally
, except
, and else
clauses in the same statement. That is,
we can now write a statement of this form:
try: # Merged formmain-action
except Exception1:handler1
except Exception2:handler2
... else:else-block
finally:finally-block
The code in this statement’s
main-action
block is executed first, as
usual. If that code raises an exception, all the except
blocks are tested, one after another,
looking for a match to the exception raised. If the exception raised
is Exception1
, the
handler1
block is executed; if it’s
Exception2
,
handler2
is run, and so on. If no exception
is raised, the else-block
is
executed.
No matter what’s happened previously, the
finally-block
is executed once the main
action block is complete and any raised exceptions have been handled.
In fact, the code in the finally-block
will
be run even if there is an error in an exception handler or the
else-block
and a new exception is
raised.
As always, the finally
clause
does not end the exception—if an exception is active when the
finally-block
is executed, it continues to
be propagated after the finally-block
runs,
and control jumps somewhere else in the program (to another try
, or to the default top-level handler).
If no exception is active when the finally
is run, control resumes after the
entire try
statement.
The net effect is that the finally
is always run, regardless of
whether:
An exception occurred in the main action and was handled.
An exception occurred in the main action and was not handled.
No exceptions occurred in the main action.
A new exception was triggered in one of the handlers.
Again, the finally
serves to
specify cleanup actions that must always occur on the way out of the
try
, regardless of what exceptions
have been raised or handled.
When combined like this, the try
statement must have either an except
or a finally
, and the order of its parts must
be like this:
try -> except -> else -> finally
where the else
and finally
are optional, and there may be
zero or more except
, but there
must be at least one except
if an
else
appears. Really, the
try
statement consists of two
parts: except
s with an optional
else
, and/or the finally
.
In fact, it’s more accurate to describe the merged statement’s syntactic form this way (square brackets mean optional and star means zero-or-more here):
try: # Format 1statements
except [type
[asvalue
]]: # [type [, value]] in Python 2statements
[except [type
[asvalue
]]:statements
]* [else:statements
] [finally:statements
] try: # Format 2statements
finally:statements
Because of these rules, the else
can appear only if there is at least
one except
, and it’s always
possible to mix except
and
finally
, regardless of whether an
else
appears or not. It’s also
possible to mix finally
and
else
, but only if an except
appears too (though the except
can omit an exception name to catch
everything and run a raise
statement, described later, to reraise the current exception). If
you violate any of these ordering rules, Python will raise a syntax
error exception before your code runs.
Prior to Python 2.5, it is actually possible to combine finally
and except
clauses in a try
by syntactically nesting a try
/except
in the try
block of a try
/finally
statement (we’ll explore this
technique more fully in Chapter 35). In fact, the following has
the same effect as the new merged form shown at the start of this
section:
try: # Nested equivalent to merged form try:main-action
except Exception1:handler1
except Exception2:handler2
... else:no-error
finally:cleanup
Again, the finally
block is
always run on the way out, regardless of what happened in the main
action and regardless of any exception handlers run in the nested
try
(trace through the four cases
listed previously to see how this works the same). Since an else
always requires an except
, this nested form even sports the
same mixing constraints of the unified statement form outlined in
the preceding section.
However, this nested equivalent is more obscure and requires
more code than the new merged form (one four-character line, at
least). Mixing finally
into the
same statement makes your code easier to write and read, so this is
the generally preferred technique today.
Here’s a demonstration of the merged try
statement form at work. The following
file, mergedexc.py, codes four common scenarios,
with print
statements that
describe the meaning of each:
sep = '-' * 32 + ' ' print(sep + 'EXCEPTION RAISED AND CAUGHT') try: x = 'spam'[99] except IndexError: print('except run') finally: print('finally run') print('after run') print(sep + 'NO EXCEPTION RAISED') try: x = 'spam'[3] except IndexError: print('except run') finally: print('finally run') print('after run') print(sep + 'NO EXCEPTION RAISED, WITH ELSE') try: x = 'spam'[3] except IndexError: print('except run') else: print('else run') finally: print('finally run') print('after run') print(sep + 'EXCEPTION RAISED BUT NOT CAUGHT') try: x = 1 / 0 except IndexError: print('except run') finally: print('finally run') print('after run')
When this code is run, the following output is produced in
Python 3.0 (actually, its behavior and output are the same in 2.6,
because the print
calls each
print a single item). Trace through the code to see how exception
handling produces the output of each of the four tests here:
c:misc> C:Python30python mergedexc.py
--------------------------------
EXCEPTION RAISED AND CAUGHT
except run
finally run
after run
--------------------------------
NO EXCEPTION RAISED
finally run
after run
--------------------------------
NO EXCEPTION RAISED, WITH ELSE
else run
finally run
after run
--------------------------------
EXCEPTION RAISED BUT NOT CAUGHT
finally run
Traceback (most recent call last):
File "mergedexc.py", line 36, in <module>
x = 1 / 0
ZeroDivisionError: int division or modulo by zero
This example uses built-in operations in the main action to trigger exceptions (or not), and it relies on the fact that Python always checks for errors as code is running. The next section shows how to raise exceptions manually instead.
To trigger exceptions explicitly, you can code raise
statements. Their general form is
simple—a raise
statement consists
of the word raise
, optionally
followed by the class to be raised or an instance of it:
raise <instance
> # Raise instance of class raise <class
> # Make and raise instance of class raise # Reraise the most recent exception
As mentioned earlier, exceptions are always instances of classes
in Python 2.6 and 3.0. Hence, the first raise
form here is the most common—we
provide an instance directly, either created
before the raise
or within the
raise
statement itself. If we pass
a class instead, Python calls the class with no
constructor arguments, to create an instance to be raised; this form
is equivalent to adding parentheses after the class reference. The
last form reraises the most recently raised exception; it’s commonly
used in exception handlers to propagate exceptions that have been
caught.
To make this clearer, let’s look at some examples. With built-in exceptions, the following two forms are equivalent—both raise an instance of the exception class named, but the first creates the instance implicitly:
raise IndexError # Class (instance created) raise IndexError() # Instance (created in statement)
We can also create the instance ahead of time—because the
raise
statement accepts any kind of
object reference, the following two examples raise IndexError
just like the prior two:
exc = IndexError() # Create instance ahead of time
raise exc
excs = [IndexError, TypeError]
raise excs[0]
When an exception is raised, Python sends the raised instance
along with the exception. If a try
includes an except name as X:
clause, the variable X
will be
assigned the instance provided in the raise
:
try:
...
except IndexError as X: # X assigned the raised instance object
...
The as
is optional in a
try
handler (if it’s omitted, the
instance is simply not assigned to a name), but including it allows
the handler to access both data in the instance and methods in the
exception class.
This model works the same for user-defined exceptions we code with classes—the following, for example, passes to the exception class constructor arguments that become available in the handler through the assigned instance:
class MyExc(Exception): pass ... raise MyExc('spam') # Exception class with constructor args ... try: ... except MyExc as X: # Instance attributes available in handler print(X.args)
Because this encroaches on the next chapter’s topic, though, I’ll defer further details until then.
Regardless of how you name them, exceptions are always
identified by instance objects, and at most one is active at any given
time. Once caught by an except
clause anywhere in the program, an exception dies (i.e., won’t
propagate to another try
), unless
it’s reraised by another raise
statement or error.
A raise
statement that does
not include an exception name or extra data value simply reraises
the current exception. This form is typically used if you need to
catch and handle an exception but don’t want the exception to die in
your code:
>>>try:
...raise IndexError('spam')
# Exceptions remember arguments ...except IndexError:
...print('propagating')
...raise
# Reraise most recent exception ... propagating Traceback (most recent call last): File "<stdin>", line 2, in <module> IndexError: spam
Running a raise
this way
reraises the exception and propagates it to a higher handler (or the
default handler at the top, which stops the program with a standard
error message). Notice how the argument we passed to the exception
class shows up in the error messages; you’ll learn why this happens
in the next chapter.
Python 3.0 (but not 2.6) also allows raise
statements to have an optional
from
clause:
raise exception from otherexception
When the from
is used, the
second expression specifies another exception class or instance to
attach to the raised exception’s __cause__
attribute. If the raised
exception is not caught, Python prints both exceptions as part of
the standard error message:
>>>try:
...1 / 0
...except Exception as E:
...raise TypeError('Bad!') from E
... Traceback (most recent call last): File "<stdin>", line 2, in <module> ZeroDivisionError: int division or modulo by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "<stdin>", line 4, in <module> TypeError: Bad!
When an exception is raised inside an exception handler, a
similar procedure is followed implicitly: the previous exception is
attached to the new exception’s __context__
attribute and is
again displayed in the standard error message if the exception goes
uncaught. This is an advanced and still somewhat obscure extension,
so see Python’s manuals for more details.
Version skew note: Python 3.0 no longer supports the raise
Exc
,
Args
form that is still available in
Python 2.6. In 3.0, use the raise
Exc
(
Args
)
instance-creation call form described
in this book instead. The equivalent comma form in 2.6 is legacy
syntax provided for compatibility with the now defunct
string-based exceptions model, and it’s deprecated in 2.6. If
used, it is converted to the 3.0 call form. As in earlier
releases, a raise
Exc
form is also allowed—it is
converted to raise
Exc
()
in both versions, calling the class
constructor with no arguments.
As a somewhat special case for debugging purposes, Python includes the
assert
statement. It is mostly just
syntactic shorthand for a common raise
usage pattern, and an assert
can be thought of as a
conditional raise
statement. A statement of the
form:
assert <test
>, <data
> # The <data
> part is optional
works like the following code:
if __debug__: if not <test
>: raise AssertionError(<data
>)
In other words, if the test evaluates to false, Python raises an
exception: the data item (if it’s provided) is used as the exception’s
constructor argument. Like all exceptions, the AssertionError
exception will kill your
program if it’s not caught with a try
, in which case the data item shows up as
part of the error message.
As an added feature, assert
statements may be removed from a compiled program’s byte code if the
-O
Python command-line flag is
used, thereby optimizing the program. AssertionError
is a built-in exception, and
the __debug__
flag is a built-in
name that is automatically set to True
unless the -O
flag is used. Use a command line like
python –O main.py
to run in
optimized mode and disable asserts.
Assertions are typically used to verify program conditions during development.
When displayed, their error message text automatically includes
source code line information and the value listed in the assert
statement. Consider the file
asserter.py:
def f(x): assert x < 0, 'x must be negative' return x ** 2 %python
>>>import asserter
>>>asserter.f(1)
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "asserter.py", line 2, in f assert x < 0, 'x must be negative' AssertionError: x must be negative
It’s important to keep in mind that assert
is mostly intended for trapping
user-defined constraints, not for catching genuine programming
errors. Because Python traps programming errors itself, there is
usually no need to code assert
s
to catch things like out-of-bounds indexes, type mismatches, and
zero divides:
def reciprocal(x): assert x != 0 # A useless assert! return 1 / x # Python checks for zero automatically
Such assert
s are generally
superfluous—because Python raises exceptions on errors
automatically, you might as well let it do the job for you.[76] For another example of common assert
usage, see the abstract superclass
example in Chapter 28; there, we used
assert
to make calls to undefined
methods fail with a message.
Python 2.6 and 3.0 introduced a new exception-related statement—the with
, and its optional as
clause. This statement is designed to
work with context manager objects, which support a new method-based
protocol. This feature is also available as an option in 2.5, enabled
with an import
of this form:
from __future__ import with_statement
In short, the with
/as
statement is designed to be an
alternative to a common try
/finally
usage idiom; like that statement, it
is intended for specifying termination-time or “cleanup” activities
that must run regardless of whether an exception occurs in a
processing step. Unlike try
/finally
, though, the with
statement supports a richer object-based protocol for specifying
both entry and exit actions around a block of code.
Python enhances some built-in tools with context managers, such as files that automatically close themselves and thread locks that automatically lock and unlock, but programmers can code context managers of their own with classes, too.
The basic format of the with
statement looks like this:
withexpression
[asvariable
]:with-block
The expression
here is assumed to
return an object that supports the context management protocol (more
on this protocol in a moment). This object may also return a value
that will be assigned to the name
variable
if the optional as
clause is present.
Note that the variable
is not
necessarily assigned the result of the
expression
; the result of the
expression
is the object that supports
the context protocol, and the variable
may be assigned something else intended to be used inside the
statement. The object returned by the
expression
may then run startup code
before the with-block
is started, as well
as termination code after the block is done, regardless of whether
the block raised an exception or not.
Some built-in Python objects have been augmented to support
the context management protocol, and so can be used with the
with
statement. For example, file
objects (covered in Chapter 9) have a context
manager that automatically closes the file after the with
block regardless of whether an
exception is raised:
with open(r'C:miscdata') as myfile:
for line in myfile:
print(line)
...more code here...
Here, the call to open
returns a simple file object that is assigned to the name myfile
. We can use myfile
with the usual file tools—in this
case, the file iterator reads line by line in the for
loop.
However, this object also supports the context management
protocol used by the with
statement. After this with
statement has run, the context management machinery guarantees that
the file object referenced by myfile
is automatically closed, even if
the for
loop raised an exception
while processing the file.
Although file objects are automatically closed on garbage
collection, it’s not always straightforward to know when that will
occur. The with
statement in this
role is an alternative that allows us to be sure that the close will
occur after execution of a specific block of code. As we saw
earlier, we can achieve a similar effect with the more general and
explicit try/finally
statement,
but it requires four lines of administrative code instead of one in
this case:
myfile = open(r'C:miscdata')
try:
for line in myfile:
print(line)
...more code here...
finally:
myfile.close()
We won’t cover Python’s multithreading modules in this
book (for more on that topic, see follow-up application-level texts
such as Programming
Python), but the lock and condition synchronization objects they define
may also be used with the with
statement, because they support the context management
protocol:
lock = threading.Lock()
with lock:
# critical section of code
...access shared resources...
Here, the context management machinery guarantees that the lock is automatically acquired before the block is executed and released once the block is complete, regardless of exception outcomes.
As introduced in Chapter 5, the
decimal
module also uses context
managers to simplify saving and restoring the current decimal
context, which specifies the precision and rounding characteristics
for calculations:
with decimal.localcontext() as ctx: ctx.prec = 2 x = decimal.Decimal('1.00') / decimal.Decimal('3.00')
After this statement runs, the current thread’s context
manager state is automatically restored to what it was before the
statement began. To do the same with a try
/finally
, we would need to save the context
before and restore it manually.
Although some built-in types come with context managers, we can also
write new ones of our own. To implement context managers, classes
use special methods that fall into the operator overloading category
to tap into the with
statement.
The interface expected of objects used in with
statements is somewhat complex, and
most programmers only need to know how to use existing context
managers. For tool builders who might want to write new
application-specific context managers, though, let’s take a quick
look at what’s involved.
Here’s how the with
statement actually works:
The expression is evaluated, resulting in an object known
as a context manager that must have
__enter__
and
__exit__
methods.
The context manager’s __enter__
method is called. The value
it returns is assigned to the variable in the as
clause if present, or simply
discarded otherwise.
The code in the nested with
block is executed.
If the with
block
raises an exception, the __exit__(
type
,
value
,
traceback
)
method is called with the exception
details. Note that these are the same values returned by
sys.exc_info
, described in the Python
manuals and later in this part of the book. If this method
returns a false value, the exception is reraised; otherwise, the
exception is terminated. The exception should normally be
reraised so that it is propagated outside the with
statement.
If the with
block does
not raise an exception, the __exit__
method is still called, but its type
,
value
, and
traceback
arguments are all passed in
as None
.
Let’s look at a quick demo of the protocol in action. The
following defines a context manager object that traces the entry and
exit of the with
block in any
with
statement it is used
for:
class TraceBlock:
def message(self, arg):
print('running', arg)
def __enter__(self):
print('starting with block')
return self
def __exit__(self, exc_type, exc_value, exc_tb):
if exc_type is None:
print('exited normally
')
else:
print('raise an exception!', exc_type)
return False # Propagate
with TraceBlock() as action:
action.message('test 1')
print('reached')
with TraceBlock() as action:
action.message('test 2')
raise TypeError
print('not reached')
Notice that this class’s __exit__
method returns False
to propagate the exception; deleting
the return
statement would have
the same effect, as the default None
return value of functions is False
by definition. Also notice that the
__enter__
method returns self
as the object to assign to the
as
variable; in other use cases,
this might return a completely different object instead.
When run, the context manager traces the entry and exit of the
with
statement block with its
__enter__
and __exit__
methods. Here’s the script in
action being run under Python 3.0 (it runs in 2.6, too, but prints
some extra tuple parentheses):
% python withas.py
starting with block
running test 1
reached
exited normally
starting with block
running test 2
raise an exception! <class 'TypeError'>
Traceback (most recent call last):
File "withas.py", line 20, in <module>
raise TypeError
TypeError
Context managers are somewhat advanced devices for tool
builders, so we’ll skip additional details here (see Python’s
standard manuals for the full story—for example, there’s a new
contextlib
standard module that
provides additional tools for coding context managers). For simpler
purposes, the try
/finally
statement provides sufficient
support for termination-time activities.
In the upcoming Python 3.1 release, the with
statement may also specify multiple
(sometimes referred to as “nested”) context managers with new
comma syntax. In the following, for example, both files’ exit
actions are automatically run when the statement block exits,
regardless of exception outcomes:
with open('data') as fin, open('res', 'w') as fout: for line in fin: if 'some key' in line: fout.write(line)
Any number of context manager items may be listed, and
multiple items work the same as nested with
statements. In general, the 3.1
(and later) code:
with A() as a, B() as b:
...statements...
is equivalent to the following, which works in 3.1, 3.0, and 2.6:
with A() as a:
with B() as b:
...statements...
In this chapter, we took a more detailed look at exception
processing by exploring the statements related to exceptions in
Python: try
to catch them, raise
to trigger them, assert
to raise them conditionally, and
with
to wrap code blocks in context
managers that specify entry and exit actions.
So far, exceptions probably seem like a fairly lightweight tool, and in fact, they are; the only substantially complex thing about them is how they are identified. The next chapter continues our exploration by describing how to implement exception objects of your own; as you’ll see, classes allow you to code new exceptions specific to your programs. Before we move ahead, though, let’s work though the following short quiz on the basics covered here.
What is the try
statement
for?
What are the two common variations of the try
statement?
What is the raise
statement for?
What is the assert
statement designed to do, and what other statement is it
like?
What is the with
/as
statement designed to do, and what
other statement is it like?
The try
statement catches
and recovers from exceptions—it specifies a block of code to run,
and one or more handlers for exceptions that may be raised during
the block’s execution.
The two common variations on the try
statement are try
/except
/else
(for catching exceptions) and
try
/finally
(for specifying cleanup actions
that must occur whether an exception is raised or not). In Python
2.4, these were separate statements that could be combined by
syntactic nesting; in 2.5 and later, except
and finally
blocks may be mixed in the same
statement, so the two statement forms are merged. In the merged
form, the finally
is still run
on the way out of the try
,
regardless of what exceptions may have been raised or
handled.
The raise
statement
raises (triggers) an exception. Python raises built-in exceptions
on errors internally, but your scripts can trigger built-in or
user-defined exceptions with raise
, too.
The assert
statement
raises an AssertionError
exception if a condition is false. It works like a conditional
raise
statement wrapped up in
an if
statement.
The with
/as
statement is designed to automate
startup and termination activities that must occur around a block
of code. It is roughly like a try
/finally
statement in that its exit
actions run whether an exception occurred or not, but it allows a
richer object-based protocol for specifying entry
and exit actions.
[74] As mentioned in the prior chapter, the text of error messages and stack traces tends to vary slightly over time and shells. Don’t be alarmed if your error messages don’t exactly match mine. When I ran this example in Python 3.0’s IDLE GUI, for instance, its error message text showed filenames with full absolute directory paths.
[75] Unless Python crashes completely, of course. It does a good job of avoiding this, though, by checking all possible errors as a program runs. When a program does crash hard, it is usually due to a bug in linked-in C extension code, outside of Python’s scope.
[76] In most cases, at least. As suggested earlier in the book, if a function has to perform long-running or unrecoverable actions before it reaches the place where an exception will be triggered, you still might want to test for errors. Even in this case, though, be careful not to make your tests overly specific or restrictive, or you will limit your code’s utility.