1

I'm writing some software in python and had a question on the preferred coding style of python. Imagine you have a function that takes some raw data, decodes it to a dict and prints the key-value pairs

def printdata(rawdata):
    data = decode(rawdata)
    for key, value in data.items():
        print(key, value)

This is all fine until decode starts throwing exceptions everywhere and the whole program comes crashing down. So, we use a try/catch block. But there are a couple ways of doing this and I'm wondering what method is preferred.

  1. Everything inside try

    def printdata(rawdata):
        try:
            data = decode(rawdata)
            for key, value in data.items():
               print(key, value)
        except ValueError:
            print("error")
    
  2. Only decode inside try with return

    def printdata(rawdata):
        data = None
        try:
            data = decode(rawdata)
        except ValueError:
            print("error")
            return
        for key, value in data.items():
           print(key, value)
    
  3. Only decode inside try with if

    def printdata(rawdata):
        data = None
        try:
            data = decode(rawdata)
        except ValueError:
            print("error")
        if data is not None:
            for key, value in data.items():
               print(key, value)
    

All of these methods have some advantages and disadvantages and I don't know which one to pick, and whether it really matters.

1
  • It doesn't matter too much though 1 isn't equivalent to 2 or 3. Use the one that looks good to you and that has the logic you want. Catching all ValueErrors isn't the same as only ValueErrors from decode(). Commented Apr 17, 2015 at 20:12

2 Answers 2

1

The first one is clearly the simplest, but it has a problem: If anything else in the rest of the suite could possibly raise a ValueError, it's not clear whether you caught the ValueError you expected and wanted to handle, or an unexpected one that probably means a bug in your code so you probably should have let abort and print a traceback.

When you know for sure that's not an issue, go for it.

Although really, you should almost certainly be handling the error like this:

except ValueError as e:
    print("error: {!r}".format(e))

… or something similar. That way, if you do get that impossible unexpected ValueError, you'll be able to tell from the unexpected message, instead of not knowing that you've been throwing away valid data because of a bug for the last 3 months of runs.


When that isn't appropriate, the other two ideas both work, but it's usually more idiomatic to use an else block.

def printdata(rawdata):
    try:
        data = decode(rawdata)
    except ValueError:
        print("error")
    else:
        for key, value in data.items():
           print(key, value)

If you do need to do #2 (maybe you've got, say, a mess of try statements inside try statements or something…), you don't need the data = None at the top, and shouldn't have it. There should be no way you could have gotten past the return without assigning to data. So, if somehow the impossible has happened, you want to get an exception and see that, not silently treat it as None.


In #3, the None actually is necessary. Which is a problem. The whole idea of "predeclaring" variables before setting them, then checking whether they've been set, is not only not idiomatic, it also often disguises bugs—e.g., what if None is a valid return from decode?

Sign up to request clarification or add additional context in comments.

4 Comments

Wow, I didn't even know you could have an else clause after a try/except. This is definitely the best option. Thanks! The print("error") was just to keep the example simple, no worries there.
@Dokahl: When you're trying to learn more about some language feature, always check the docs first. :)
@abarnert: I just can't believe you advise to just print the error and call it a day instead of letting it propagate to somewhere it can be handled. Stupidiest "error handling" scheme ever :(
@brunodesthuilliers: I don't think you understood the advice. Look at the very first sentence. That's the key. So, if the design goal is to log and ignore a possible specific ValueError from decode, then the most important consideration is not the style, but making sure that you are logging and ignoring only that specific error. Make doubly sure by thinking through the structure, then make triply sure by logging enough information to catch you if you didn't think of something.
0

The "prefered coding style" is to not "handle" errors unless you can really handle them. This means that at the library level, you should have almost none error handling - just let errors propagate to the application level. At the application level you want

  1. a top-level error handler that will properly log unhandled errors with the full traceback (logging.exception() is your friend), present the user a user-friendly error message and crash.

  2. where you actually can ask the user for a correction (try again, select another file, whatever), do it.

Just printing the error message - without teh full traceback etc - is just a waste of everyone time.

2 Comments

I don't think this is relevant here. Presumably he has a reason to want to log and ignore these decode errors. For example, maybe 3% of the data are garbled, and he wants to get on with processing the 97% that aren't. In that case, it makes perfect sense to write a one-line log message and move on.
More generally, this advice seems geared to a very different language than Python. In Python, exceptions get thrown all over the place. Every for loop ends with an exception. So "Always log all the information about every exception" is not appropriate for Python. "Always log all the information about every unexpected exception" is still definitely true—but the easiest way to do that, as long as it's appropriate for your app, is just to not catch unexpected exceptions and let them abort and print a traceback.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.