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

后端 未结 6 601
醉梦人生
醉梦人生 2020-12-07 18:41

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

def intersect_two_lists(self, list1, list2):
    if not list1:
        self.trap_e         


        
6条回答
  •  半阙折子戏
    2020-12-07 19:17

    The general case is to throw exceptions for exceptional circumstances. I wish that I could remember the exact quote (or who said it), but you should strive for functions that accept as many values and types as is reasonable and maintain a very narrowly defined behavior. This is a variant of what Nadia was talking about. Consider the following usages of your function:

    1. intersect_two_lists(None, None)
    2. intersect_two_lists([], ())
    3. intersect_two_lists('12', '23')
    4. intersect_two_lists([1, 2], {1: 'one', 2: 'two'})
    5. intersect_two_lists(False, [1])
    6. intersect_two_lists(None, [1])

    I would expect that (5) throws an exception since passing False is a type error. The rest of them, however, make some sort of sense but it really depends on the contract that the function states. If intersect_two_lists were defined as returning the intersection of two iterables, then everything other than (5) should work as long as you make None a valid representation of the empty set. The implementation would be something like:

    def intersect_two_lists(seq1, seq2):
        if seq1 is None: seq1 = []
        if seq2 is None: seq2 = []
        if not isinstance(seq1, collections.Iterable):
            raise TypeError("seq1 is not Iterable")
        if not isinstance(seq2, collections.Iterable):
            raise TypeError("seq1 is not Iterable")
        return filter(...)
    

    I usually write helper functions that enforce whatever the contract is and then call them to check all of the preconditions. Something like:

    def require_iterable(name, arg):
        """Returns an iterable representation of arg or raises an exception."""
        if arg is not None:
            if not isinstance(arg, collections.Iterable):
                raise TypeError(name + " is not Iterable")
            return arg
        return []
    
    def intersect_two_lists(seq1, seq2):
        list1 = require_iterable("seq1", seq1)
        list2 = require_iterable("seq2", seq2)
        return filter(...)
    

    You can also extend this concept and pass in the "policy" as an optional argument. I would not advise doing this unless you want to embrace Policy Based Design. I did want to mention it just in case you haven't looked into this option before.

    If the contract for intersect_two_lists is that it only accepts two non-empty list parameters, then be explicit and throw exceptions if the contract is breached:

    def require_non_empty_list(name, var):
        if not isinstance(var, list):
            raise TypeError(name + " is not a list")
        if var == []:
            raise ValueError(name + " is empty")
    
    def intersect_two_lists(list1, list2):
        require_non_empty_list('list1', list1)
        require_non_empty_list('list2', list2)
        return filter(...)
    

    I think that the moral of the story is whatever you do, do it consistently and be explicit. Personally, I usually favor raising exceptions whenever a contract is breached or I am given a value that I really cannot use. If the values that I am given are reasonable, then I try to do something reasonable in return. You might also want to read the C++ FAQ Lite entry on exceptions. This particular entry gives you some more food for thought about exceptions.

提交回复
热议问题