"outsourcing" exception-handling to a decorator

Question:

Many try/except/finally-clauses not only “uglify” my code, but i find myself often using identical exception-handling for similar tasks. So i was considering reducing redundancy by “outsourcing” them to a … decorator.

Because i was sure not to be the 1st one to come to this conclusion, I googled and found this – imho – ingenious recipe which added the possibility to handle more than one exception.

But i was surprised why this doesn’t seem to be a wide known and used practice per se, so i was wondering if there is maybe an aspect i wasn’t considering?

  1. Is it bogus to use the decorator pattern for exception-handling or did i just miss it the whole time? Please enlighten me! What are the pitfalls?

  2. Is there maybe even a package/module out there which supports the creation of such exception-handling in a reasonable way?

Asked By: Don Question

||

Answers:

Basically, the drawback is that you no longer get to decide how to handle the exception in the calling context (by just letting the exception propagate). In some cases this may result in a lack of separation of responsibility.

Answered By: Karl Knechtel
  1. Decorator in Python is not the same as the Decorator pattern, thought there is some similarity. It is not completely clear waht you mean here, but I think you mean the one from Python (thus, it is better not to use the word pattern)

  2. Decorators from Python are not that useful for exception handling, because you would need to pass some context to the decorator. That is, you would either pass a global context, or hide function definitions within some outer context, which requires, I would say, LISP-like way of thinking.

  3. Instead of decorators you can use contextmanagers. And I do use them for that purpose.

Answered By: newtover

The biggest reason to keep the try/except/finally blocks in the code itself is that error recovery is usually an integral part of the function.

For example, if we had our own int() function:

def MyInt(text):
    return int(text)

What should we do if text cannot be converted? Return 0? Return None?

If you have many simple cases then I can see a simple decorator being useful, but I think the recipe you linked to tries to do too much: it allows a different function to be activated for each possible exception–in cases such as those (several different exceptions, several different code paths) I would recommend a dedicated wrapper function.

Here’s my take on a simple decorator approach:

class ConvertExceptions(object):

    func = None

    def __init__(self, exceptions, replacement=None):
        self.exceptions = exceptions
        self.replacement = replacement

    def __call__(self, *args, **kwargs):
        if self.func is None:
            self.func = args[0]
            return self
        try:
            return self.func(*args, **kwargs)
        except self.exceptions:
            return self.replacement

and sample usage:

@ConvertExceptions(ValueError, 0)
def my_int(value):
    return int(value)

print my_int('34')      # prints 34
print my_int('one')     # prints 0
Answered By: Ethan Furman