Recipe4.22.Handling Exceptions Within an Expression


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):     '''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

For example, suppose you have a text file, which has one number per 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):     " 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

Unfortunately, this version doesn't fit in well in a 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):     " Return [f(*a, **k)] normally, [  ] if that raises an exception in t. "     try:         return [ f(*a, **k) ]     except t:         return [ ]

The resulting list comprehension is even more 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.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net