Best practice in python for return value on error vs. success

Viewed 101921

In general, let's say you have a method like the below.

def intersect_two_lists(self, list1, list2):
    if not list1:
        self.trap_error("union_two_lists: list1 must not be empty.")
        return False
    if not list2:
        self.trap_error("union_two_lists: list2 must not be empty.")
        return False
    #http://bytes.com/topic/python/answers/19083-standard
    return filter(lambda x:x in list1,list2)

In this particular method when errors are found, I would not want to return the empty list in this case because that could have been the real answer to this specific method call, I want to return something to indicate the parameters were incorrect. So I returned False on error in this case, and a list otherwise (empty or not).

My question is, what is the best practice in areas like this, and not just for lists?Return whatever the heck I want and make sure I document it for a user to read? :-) What do most of you folks do:

  1. If on success you were supposed to return True or False and you catch an error?
  2. If on success you were supposed to return a list and you catch an error?
  3. If on success you were supposed to return a file handle and you catch an error?
  4. et cetera
7 Answers

For collections (lists, sets, dicts, etc.) returning an empty collection is the obvious choice because it allows your call site logic to remain clear of defensive logic. More explicitly, an empty collection is still a perfectly good answer from a function from which you expect a collection, you do not have to check that the result is of any other type, and can continue your business logic in a clean manner.

For non collection results there are several ways to handle conditional returns:

  1. As many answers have already explained, using Exceptions is one way to solve this, and is idiomatic python. However, it is my preference not to use Exceptions for control flow as I find it creates ambiguity on the intentions of an Exception. Instead, raise Exceptions in actual exceptional situations.
  2. Another solution is to return None instead of your expected result but this forces a user to add defensive checks everywhere in their call sites, obfuscating the actual business logic they are trying to actually execute.
  3. A third way is to use a collection type that is only capable of holding a single element (or being explicitly empty). This is called an Optional and is my preferred method because it allows you to keep clean call site logic. However, python does not have a built in optional type, so I use my own. I published it in a tiny library called optional.py if anyone wants to give it a try. You can install it using pip install optional.py. I welcome comments, feature requests, and contributions.

There are plenty of arguments to make about why we should or should not use exceptions. Not to argue about that. But to answer the question directly, here is what we can do with Python 3.10+:

from typing import TypeVar, Generic

ResultType = TypeVar('ResultType')


# A generic success class that hold a value
class Success(Generic[ResultType]):
    def __init__(self, value: ResultType):
        self.value = value


# A 'Failure' type that hold an error message 
class Failure:
    def __init__(self, message: str):
        self.message = message


# A union type 'Result' that can either be 'Success' or 'Failure'
Result = Success[ResultType] | Failure

And here is an example of how it can be used:

# A function that will only return 'Success' when the greeting message start with "Hello" 
def make_a_call(greeting: str) -> Result:
    if greeting.startswith("Hello"):
        return Success(greeting)
    else:
        return Failure("Not polite")


if __name__ == '__main__':
    for sentence in ("Hello", "Hey"):
        response = make_a_call(sentence)
        if isinstance(response, Success):
            print("Greeting message:", response.value)
        else:
            print("Failure:", response.message)

Example Output:

Greeting message: Hello
Failure: Not polite
Related