Recipe 4.22. Handling Exceptions Within an Expression
Credit: Chris Perkins, Gregor Rayman, Scott David
Daniels
Problem
You want
to code an expression, so you
can't directly use the
statement
TRy/except, but you still need
to handle exceptions that the expression may throw.
Solution
To catch exceptions, TRy/except
is indispensable, and, since
try/except is a
statement, the only way to use it inside an
expression is to code an auxiliary
function:
def throws(t, f, *a, **k):For example, suppose you have a text file, which has one number per
'''Return True iff f(*a, **k) raises an exception whose type is t
(or, one of the items of _tuple_ t, if t is a tuple).'''
try:
f(*a, **k)
except t:
return True
else:
return False
line, but also extra lines which may be whitespace, comments, or
what-have-you. Here is how you can make a list of all the numbers in
the file, skipping the lines that aren't numbers:
data = [float(line) for line in open(some_file)
if not throws(ValueError, float, line)]
Discussion
You might prefer to name such a function raises, but
I personally prefer throws, which is probably a
throwback to C++. By whatever name, the auxiliary function shown in
this recipe takes as its arguments, first an exception type (or tuple
of exception types) t, then a callable
f, and then arbitrary positional and named arguments
a and k, which
are to be passed on to f. Do
not code, for example, if not
throws(ValueError, float(line))! When you call a
function, Python evaluates the arguments before passing control to
the function; if an argument's evaluation raises an
exception, the function never even gets started.
I've seen this erroneous usage attempted more than
once by people who are just starting to use the
assertRaises method from the standard Python
library's unittest.TestCase
class, for example.When throws executes, it just calls
f within the try clause of a
try/except statement, passing
on the arbitrary positional and named arguments. If the call to
f in the try clause raises an
exception whose type is t (or one of the items of
t, if t is a
tuple of exception types), then control passes to
the corresponding except clause, which, in this
case, returns true as
throws' result. If no exception is
raised in the try clause, then control passes to
the corresponding else clause (if any), which, in
this case, returns False as
throws' result.Note that, if some unexpected exception (one
whose type is not in t) gets raised, then function
throws does not catch that exception, so that
throws terminates and propagates the exception to
its caller. This choice is quite a deliberate one. Catching
exceptions with a too-wide except clause is a
bug-diagnosing headache waiting to happen. If the caller really wants
throws to catch just about everything, it can always
call tHRows(Exception, . . .and live with
the resulting headaches.One problem with the throws function is that you end
up doing the key operation twiceonce just to see if it throws,
tossing the result away, then, a second time, to get the result. It
would be nicer to get the result, if any, together with an indication
of whether an exception has been caught. I first tried something
along the lines of:
def throws(t, f, *a, **k):Unfortunately, this version doesn't fit in well in a
" Return a pair (True, None) if f(*a, **k) raises an exception whose
type is in t, else a pair (False, x) where x
is the result of f(*a, **k). "
try:
return False, f(*a, **k)
except t:
return True, None
list comprehension: there is no elegant way to get and use both the
flag and the result. So, I chose a different approach: a function
that returns a list in any caseempty if an
exception was caught, otherwise with the result as the only item.
This approach works fine in a list comprehension, but for clarity,
the name of the function needs to be changed:
def returns(t, f, *a, **k):The resulting list comprehension is even more
" Return [f(*a, **k)]normally,[ ]if that raises an exception in t. "
try:
return [ f(*a, **k) ]
except t:
return [ ]
elegant, in my opinion, than the original one in this
recipe's Solution:
data = [ x for line in open(some_file)
for x in returns(ValueError, float, line) ]
See Also
Python in a Nutshell's
section on catching and handling exceptions; the sidebar The *args and **kwds Syntax for an explanation of
*args and **kwds syntax.