Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the pythonic way to bubble up error conditions

I have been working on a Python project that has grown somewhat large, and has several layers of functions. Due to some boneheaded early decisions I'm finding that I have to go a fix a lot of crashers because the lower level functions are returning a type I did not expect in the higher level functions (usually None).

Before I go through and clean this up, I got to wondering what is the most pythonic way of indicating error conditions and handling them in higher functions?

What I have been doing for the most part is if a function can not complete and return its expected result, I'll return None. This gets a little gross, as you end up having to always check for None in all the functions that call it.

def lowLevel():
    ## some error occurred
    return None

    ## processing was good, return normal result string
    return resultString

def highLevel():
    resultFromLow = lowLevel()
    if not resultFromLow:
        return None

    ## some processing error occurred
    return None

    ## processing was good, return normal result string
    return resultString

I guess another solution might be to throw exceptions. With that you still get a lot of code in the calling functions to handle the exception.

Nothing seems super elegant. What do other people use? In obj-c a common pattern is to return an error parameter by reference, and then the caller checks that.

like image 551
D.C. Avatar asked May 22 '14 04:05

D.C.


1 Answers

It really depends on what you want to do about the fact this processing can't be completed. If these are really exceptions to the ordinary control flow, and you need to clean up, bail out, email people etc. then exceptions are what you want to throw. In this case the handling code is just necessary work, and is basically unavoidable, although you can rethrow up the stack and handle the errors in one place to keep it tidier.

If these errors can be tolerated, then one elegant solution is to use the Null Object pattern. Rather than returning None, you return an object that mimics the interface of the real object that would normally be returned, but just doesn't do anything. This allows all code downstream of the failure to continue to operate, oblivious to the fact there was a failure. The main downside of this pattern is that it can make it hard to spot real errors, since your code will not crash, but may not produce anything useful at the end of its run.

A common example of the Null Object pattern in Python is returning an empty list or dict when you're lower level function has come up empty. Any subsequent function using this returned value and iterating through elements will just fall through silently, without the need for error checking. Of course, if you require the list to have at least one element for some reason, then this won't work, and you're back to handling an exceptional situation again.

like image 165
ire_and_curses Avatar answered Oct 18 '22 20:10

ire_and_curses