简体   繁体   中英

Generic Exception Handling in Python the “Right Way”

Sometimes I find myself in the situation where I want to execute several sequential commands like such:

try:
    foo(a, b)
except Exception, e:
    baz(e)
try:
    bar(c, d)
except Exception, e:
    baz(e)
...

This same pattern occurs when exceptions simply need to be ignored.

This feels redundant and the excessive syntax causes it to be surprisingly difficult to follow when reading code.

In C, I would have solved this type of problem easily with a macro, but unfortunately, this cannot be done in straight python.

Question: How can I best reduce the code footprint and increase code readability when coming across this pattern?

You could use the with statement if you have python 2.5 or above:

from __future__ import with_statement
import contextlib

@contextlib.contextmanager
def handler():
    try:
        yield
    except Exception, e:
        baz(e)

Your example now becomes:

with handler():
    foo(a, b)
with handler():
    bar(c, d)

If this is always, always the behaviour you want when a particular function raises an exception, you could use a decorator:

def handle_exception(handler):
    def decorate(func):
        def call_function(*args, **kwargs):
            try:
                func(*args, **kwargs)
            except Exception, e:
                handler(e)
        return call_function
    return decorate

def baz(e):
    print(e)

@handle_exception(baz)
def foo(a, b):
    return a + b

@handle_exception(baz)
def bar(c, d):
    return c.index(d)

Usage:

>>> foo(1, '2')
unsupported operand type(s) for +: 'int' and 'str'
>>> bar('steve', 'cheese')
substring not found

If they're simple one-line commands, you can wrap them in lambda s:

for cmd in [
    (lambda: foo (a, b)),
    (lambda: bar (c, d)),
]:
    try:
        cmd ()
    except StandardError, e:
        baz (e)

You could wrap that whole thing up in a function, so it looked like this:

ignore_errors (baz, [
    (lambda: foo (a, b)),
    (lambda: bar (c, d)),
])

The best approach I have found, is to define a function like such:

def handle_exception(function, reaction, *args, **kwargs):
    try:
        result = function(*args, **kwargs)
    except Exception, e:
        result = reaction(e)
    return result

But that just doesn't feel or look right in practice:

handle_exception(foo, baz, a, b)
handle_exception(bar, baz, c, d)

You could try something like this. This is vaguely C macro-like.

class TryOrBaz( object ):
    def __init__( self, that ):
        self.that= that
    def __call__( self, *args ):
        try:
            return self.that( *args )
        except Exception, e:
            baz( e )

TryOrBaz( foo )( a, b )
TryOrBaz( bar )( c, d )

In your specific case, you can do this:

try:
    foo(a, b)
    bar(c, d)
except Exception, e:
    baz(e)

Or, you can catch the exception one step above:

try:
    foo_bar() # This function can throw at several places
except Exception, e:
    baz(e)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM