python中关于错误与成功的返回值的最佳实践

时间:2009-10-27 13:11:16

标签: python return

一般中,假设您有类似下面的方法。

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)

在这个特殊的方法中,当发现错误时,我不想在这种情况下返回空列表,因为这可能是这个特定方法调用的真正答案,我想返回一些东西来指示参数不正确。所以我在这种情况下错误地返回False,否则返回一个列表(空或不)。

我的问题是,在这样的领域中最好的做法是什么,而不仅仅是列表?返回我想要的任何内容并确保我将其记录下来供用户阅读? :-)你们大多数人都做了什么:

  1. 如果成功,你应该返回True或False并且你发现错误?
  2. 如果成功,你应该返回一个列表,然后发现错误?
  3. 如果成功,你应该返回一个文件句柄,然后发现错误?
  4. 等等

6 个答案:

答案 0 :(得分:56)

首先,无论你做什么都不会返回结果和错误信息。这是处理错误的一种非常糟糕的方法,会让你无休止的头痛。 如果您需要指出错误,请始终引发异常

除非有必要,否则我通常会避免提出错误。在您的示例中,实际上不需要抛出错误。将空列表与非空列表相交并不是错误。结果只是空列表,这是正确的。但是,假设你想处理其他案件。例如,如果方法获得非列表类型。在这种情况下,最好提出异常。例外没有什么可担心的。

我的建议是查看Python库中的类似函数,看看Python如何处理这些特殊情况。例如,看看集合中的交集方法,它往往是宽容的。在这里,我试图将空集与空列表相交:

>>> b = []
>>> a = set()
>>> a.intersection(b)
set([])

>>> b = [1, 2]
>>> a = set([1, 3])
>>> a.intersection(b)
set([1])

只有在需要时才会抛出错误:

>>> b = 1
>>> a.intersection(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable

当然,有些情况下,成功或失败时返回True或False都可能是好的。但要保持一致非常重要。该函数应始终返回相同的类型或结构。拥有一个可以返回列表或布尔值的函数是非常令人困惑的。或者返回相同的类型,但如果出现错误,此值的含义可能会有所不同。

修改

OP说:

  

我想要回复一些事情   参数不正确。

没有什么比异常更好的错误。如果要指示参数不正确,请使用异常并输入有用的错误消息。在这种情况下返回结果只是令人困惑。在其他情况下,您可能希望指出没有发生任何事情,但这不是错误。例如,如果您有一个方法从表中删除条目,并且请求删除的条目不存在。在这种情况下,成功或失败时返回True或False可能没问题。这取决于应用程序和预期的行为

答案 1 :(得分:20)

raise an exception比返回特殊值更好。这正是设计的异常,用更健壮和结构化的错误处理机制替换错误代码。

class IntersectException(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg

def intersect_two_lists(self, list1, list2):
    if not list1: raise IntersectException("list1 must not be empty.")
    if not list2: raise IntersectException("list2 must not be empty.")

    #http://bytes.com/topic/python/answers/19083-standard
    return filter(lambda x:x in list1,list2)

在这种特殊情况下,我可能只是放弃测试。真的,交叉空列表没有错。此外,lambda有点灰心,而不是列表推导。如果不使用lambda来编写此代码,请参阅Find intersection of two lists?

答案 2 :(得分:13)

我喜欢返回一个元组:

  

(是的,some_result)

     

(False,some_useful_response)

some_useful_response 对象可用于处理返回条件,也可用于显示调试信息。

注意:此技术适用于任何种类的返回值。它不应该与例外情况错误。

在接收端,您只需要解压缩:

  

代码,响应= some_function(...)

此技术适用于“正常”控制流:当发生某些意外输入/处理时,必须使用异常功能。

还值得注意:此技术有助于规范化函数返回。程序员和函数用户都知道会发生什么

免责声明:我来自Erlang背景: - )

答案 3 :(得分:11)

例外肯定比状态返回更好(和更多Pythonic)。有关此问题的更多信息:Exceptions vs. status returns

答案 4 :(得分:3)

一般情况是针对特殊情况抛出异常。我希望我能记住确切的引用(或谁说出来),但你应该努力寻找能够接受尽可能多的值和类型的函数,并保持一个非常狭义的行为。这是Nadia was talking about的变体。请考虑以下函数用法:

  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])
  7. 我希望(5)抛出一个异常,因为传递False是一个类型错误。然而,其余的都有某种意义,但它实际上取决于函数所述的契约。如果将intersect_two_lists定义为返回两个iterables的交集,则只要您使None成为空集<的有效表示,则除了(5)之外的所有内容都应该有效/ em>的。实现类似于:

    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(...)
    

    我通常编写帮助函数来强制执行合同,然后调用它们来检查所有前提条件。类似的东西:

    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(...)
    

    您还可以扩展此概念并将“policy”作为可选参数传递。除非你想拥抱Policy Based Design,否则我不建议这样做。我确实想提一下,以防你之前没有考虑过这个选项。

    如果intersect_two_lists的合同是它只接受两个非空的list参数,那么如果合同被违反则明确并抛出异常:

    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(...)
    

    我认为故事的寓意是无论你做什么,一贯做到并明确。就个人而言,我通常倾向于在违反合同时提出异常,或者给予我真正无法使用的价值。如果给出的值是合理的,那么我会尝试做一些合理的回报。您可能还想阅读有关例外的C++ FAQ Lite entry。这个特殊条目为您提供了一些关于异常的思考的食物

答案 5 :(得分:1)

对于集合(列表,集合,字典等),返回空集合是显而易见的选择,因为它使您的呼叫站点逻辑免于防御逻辑。更明确地讲,空集合仍然是您希望从中获得集合的函数的完美答案,您不必检查结果是否为任何其他类型,并且可以干净地继续您的业务逻辑。 >

对于非收款结果,有几种处理条件回报的方法:

  1. 正如许多答案所解释的那样,使用异常是解决此问题的一种方法,它是惯用的python。但是,我倾向于不对控制流使用异常,因为我发现它对异常的意图产生了歧义。相反,在实际特殊情况下引发异常。
  2. 另一种解决方案是返回None 而不是您的预期结果,但这迫使用户在其呼叫站点的所有位置添加防御性检查,从而混淆了他们试图实际执行的实际业务逻辑执行。
  3. 第三种方法是使用只能容纳单个元素的集合类型(或明确为空)。这称为“可选”,是我的首选方法,因为它使您可以保持干净的呼叫站点逻辑。但是,python没有内置的可选类型,因此我使用了自己的。如果有人想尝试一下,我将其发布在一个名为optional.py的小型图书馆中。您可以使用pip install optional.py安装它。我欢迎评论,功能要求和贡献。