2

I'm trying to get my head around Python exceptions. I've read quite a bit on the topic but can't get clear answers to some questions. In particular, I'm still not sure whether to use exceptions or guard clauses for dealing with invalid arguments.

I know that this topic is, in general, a big debate with no clear answer. But I'm interested in whether Python in particular, as a language or as a community, has adopted a clear "best practice" answer.

It seems that Python is more lax with exceptions and in fact encourages them to be used for control flow. E.g., the StopIterationError. Does this extend to using exceptions to handle argument validation?

Here is a sample function I'm looking at:

def add_document(data: dict, country: str):
    """Writes document to the database.
    """

    # Guard clauses
    if data.get('Name', None) is None: log.warn("Name is none. Returning"); return
    if data.get('url', None) is None: log.warn("URL is none. Returning"); return
    <do write>

To me, getting an invalid argument is not "exceptional". It is actually expected to happen about 10% of the time. And yet, I still seem to want to warn the user/programmer about it (I wrote this function a year ago and am looking at it again now). Should it therefore fail more loudly? Does Python Zen "Errors should never pass silently." apply here? Or should exceptions still be reserved for cases where either they have to be handled, or the software must be made to terminate?

1
  • 1
    I like @Caleth answer. But another fix would be to reconsider why the client has to setup a fiddly dict that they will fail 10% of the time. Should something else be "in charge" of the dict with the database Name and url? (note the inconsistent capitalization!)
    – user949300
    Jun 19, 2019 at 18:27

2 Answers 2

8

Does this extend to using exceptions to handle argument validation?

Even more so. Consider C#, which discourages exceptions for control flow, but encourages enforcing function preconditions with exceptions.

Should it therefore fail more loudly? Does Python Zen "Errors should never pass silently." apply here?

Yes. As it stands the caller has no indication of the success or failure of add_document.

Or should exceptions still be reserved for cases where either they have to be handled, or the software must be made to terminate?

Every result always "has to be handled". Failure states are no exception.

5

I'm still not sure whether to use exceptions or guard clauses for dealing with invalid arguments.

Guard clauses and exceptions aren't mutually-exclusive. Failing the condition in a guard clause is often grounds for throwing an exception. In a lot of ways, something like guard_against_none(value) is a shorthand for "throw something if value is None."

It seems that Python is more lax with exceptions and in fact encourages them to be used for control flow. E.g., the StopIterationError. Does this extend to using exceptions to handle argument validation?

Don't get laxity and a different paradigm confused.

A number of languages people tend to learn early these days impose a significant penalty for using exceptions. That fosters a general "exceptions considered harmful," mentality that sticks because there's no context for it. Nobody bothers to tell noobs that it's a limitation in the language and not with exceptions in general.

Python is not one of those languages. Exceptions are cheap and efficient enough that using them for control flow is just fine and can make for easier-to-read code.

To me, getting an invalid argument is not "exceptional".

This code would beg to disagree:

def divide(dividend, divisor):
    """Do a division.  The divisor shouldn't be zero."""
    return dividend / divisor

print divide(12345, 0)

Even if you don't check divisor, the divide operation in the returned expression is going to raise a ZeroDivisionError for you. That says that a division by zero happened, but it doesn't give the caller any hint whether it was because of a bug in the function or because the divisor passed into it wasn't valid according to its documentation.

It is actually expected to happen about 10% of the time. ... Should it therefore fail more loudly?

Yes. No. Maybe. That's a decision you have to make based on your circumstances.

This specific case turns the invalid into the valid. The guard clauses as you wrote them don't really guard against anything. They codify an undocumented semantic that says the function will return silently without doing anything if given invalid input. This needs to be part of the function documentation so anyone calling the function understands the potential pitfall.

If your program contains a mix of cases, you need different functions that provide different behaviors:

def add_document(data, country):
    """Add a document."""
    if <invalid-argument-condition(s)>:
        raise ValueError("Invalid argument(s).")
    # Add the document

def quietly_add_document(data, country):
    """Add a document, ignoring invalid input by doing nothing."""
    try:
        add_document(data, country)
    except ValueError:
        pass  # Don't care if arguments were bogus.

Does Python Zen "Errors should never pass silently." apply here?

I'm not one for "never x"-type dicta; in this case I'd say it does apply in an "errors should never pass silently except where they should" sort of way.

Or should exceptions still be reserved for cases where either they have to be handled, or the software must be made to terminate?

Handling and termination are the only possible outcomes of an exception. Termination is there for cases where you know something wrong and none of the callers all the way up to the main program knows how to deal with it.

0

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.