6

Objective: I have several lines of code each capable of producing the same type of error, and warranting the same kind of response. How do I prevent a 'do not repeat yourself' problem with the try-except blocks.

Background:

I using ReGex to scrape poorly formatted data from a text file, and input it into the field of a custom object. The code works great except when the field has been left blank in which case it throws an error.

I handle this error in a try-except block. If error, insert a blank into the field of the object (i.e. '').

The problem is it turns easily readable, nice, Python code into a mess of try-except blocks that each do exact same thing. This is a 'do not repeat yourself' (a.k.a. DRY) violation.

The Code:

Before:

sample.thickness = find_field('Thickness', sample_datum)[0]
sample.max_tension = find_field('Maximum Load', sample_datum)[0]
sample.max_length = find_field('Maximum Extension', sample_datum)[0]
sample.test_type = sample_test

After:

try:
    sample.thickness = find_field('Thickness', sample_datum)[0]
except:
    sample.thickness = ''

try:
    sample.max_tension = find_field('Maximum Load', sample_datum)[0]
except:
    sample.max_tension = ''

try:
    sample.max_length = find_field('Maximum Extension', sample_datum)[0]
except: 
    sample.max_length = ''

try:    
    sample.test_type = sample_test
except:
    sample.test_type = ''

What I Need:

Is there some Pythonic way to write this? Some block where I can say if there is an index-out-of-range error on any of these lines (indicating the field was blank, and ReGex failed to return anything) insert a blank in the sample field.

Michael Molter
  • 1,296
  • 2
  • 14
  • 37
  • What kind of error does it throw? – Moses Koledoye Jul 20 '16 at 18:48
  • An out of index. The find field function returns a list of the ReGex search results. If ReGex doesn't find anything, then the list it returns is empty. When I try to nab the first result I get that exception. – Michael Molter Jul 20 '16 at 18:49
  • 1
    Come to think about it, the answer to this particular problem is trivial. Modify the `find_field()` function to return a blank on a failed ReGex search; however, I have had similar programming problems before thus the answer to this question remains relevant to others. – Michael Molter Jul 20 '16 at 18:50
  • Just curious: What is ReGex? Seems like an awfully terrible name since it's so ungoogleable. – Stefan Pochmann Jul 20 '16 at 19:04
  • RegEx regular expressions. – Michael Molter Jul 20 '16 at 19:24

5 Answers5

8

What about extracting a function out of it?

def maybe_find_field(name, datum):
    try:
        return find_field(name, datum)[0]
    except IndexError: # Example of specific exception to catch
        return ''

sample.thickness = maybe_find_field('Thickness', sample_datum)
sample.max_tension = maybe_find_field('Maximum Load', sample_datum)
sample.max_length = maybe_find_field('Maximum Extension', sample_datum)
sample.test_type = sample_test

BTW, don't simply catch all possible exceptions with except: unless that's really what you want to do. Catching everything may hide implementation errors and become quite difficult to debug later. Whenever possible, bind your except case to the specific exception that you need.

Matheus Portela
  • 2,420
  • 1
  • 21
  • 32
5

Not quite matching the question, but Google sent me here and so others might come.

I have post functions in two separate Django-views, which call similar backend-functions and require the same exception-handling.

I solved the issue by extracting the whole except:-tree and sticking it into a decorator.

Before:

# twice this
def post(request):
    try:
        return backend_function(request.post)
    except ProblemA as e:
        return Response("Problem A has occurred, try this to fix it.", status=400)
    except ProblemB as e:
        return Response("Problem B has occurred, try this to fix it.", status=400)
    except ProblemC as e:
        return Response("Problem C has occurred, try this to fix it.", status=400)
    except ProblemD as e:
        return Response("Problem D has occurred, try this to fix it.", status=400)

After:

# once this
def catch_all_those_pesky_exceptions(original_function):
    def decorated(*args, **kwargs):
        try:
            return original_function(*args, **kwargs)
        except ProblemA as e:
            return Response("Problem A has occurred, try this to fix it.", status=400)
        except ProblemB as e:
            return Response("Problem B has occurred, try this to fix it.", status=400)
        except ProblemC as e:
            return Response("Problem C has occurred, try this to fix it.", status=400)
        except ProblemD as e:
            return Response("Problem D has occurred, try this to fix it.", status=400)

    return decorated

# twice this - note the @decorator matching the above function.
@catch_all_those_pesky_exceptions
def post(request):
    return backend_function(request.post)

Now I can add more exception-handling in a single place.

Chris
  • 5,788
  • 4
  • 29
  • 40
1

When you find yourself repeating code, encapsulate it in a function. In this case, create a function that handles the exception for you.

def try_find_field(field_name, datum, default_value):
    try:
        return find_field(field_name, datum)[0]
    except:
        return default_value
mtadd
  • 2,495
  • 15
  • 18
1

What about something like this:

def exception_handler(exception_class):
    logger = logging.getLogger('app_error')
    logger.error(exception_class)

    exception_name = exception_class.__name__

    if exception_name == 'AuthenticationError':
        raise AuthenticationError
    elif exception_name == 'AuthorizationError':
        raise AuthorizationError
    elif exception_name == 'ConnectionError':
        raise ConnectionError
    else:
        raise GenericError

def call_external_api()
    try:
        result = http_request()
    except Exception as e:
        exception_handler(exception_class=type(e))
Braden Holt
  • 1,544
  • 1
  • 18
  • 32
  • The problem of using `type(e)` to determine an exception class is that it won't work with its sub-classes, so for example any subclass of `ConnectionError` would raise a `GenericError`. – MestreLion Mar 15 '21 at 08:27
0

You can have any number of except blocks over and over, handling different kinds of exceptions. There's also nothing wrong with having multiple statements in the same try/catch block.

try:
    doMyDangerousThing()
except ValueError:
    print "ValueError!"
except HurrDurrError:
    print "hurr durr, there's an error"

try:
    doMyDangerousThing()
    doMySecondDangerousThing()
except:
    print "Something went wrong!"
Athena
  • 3,200
  • 3
  • 27
  • 35
  • While I am appreciative of your feedback, I feel this answer misses the entire point of the question. I do not have a need to handle different exceptions differently (in fact any error produces a blank), but instead asked how I could clean up code in which there were a series of nearly identical try-except blocks. – Michael Molter Jul 22 '16 at 12:48