在Python中,我不时看到块:
try:
try_this(whatever)
except SomeException as exception:
#Handle exception
else:
return something
除了存在其他尝试之外,其他尝试的原因是什么?
我不喜欢这种编程,因为它使用异常来执行流控制。但是,如果它包含在语言中,那一定有一个很好的理由,不是吗?
我的理解是,异常不是错误,它们只应用于异常情况(例如,我试图将文件写入磁盘,但没有更多空间,或者可能没有权限),而不用于流控制。
通常情况下,我将异常处理为:
something = some_default_value
try:
something = try_this(whatever)
except SomeException as exception:
#Handle exception
finally:
return something
或者如果发生异常时我真的不想返回任何内容,则:
try:
something = try_this(whatever)
return something
except SomeException as exception:
#Handle exception
try
块允许您处理预期的错误。except
块应该只捕获准备处理的异常。如果处理意外错误,代码可能会做错误的事情并隐藏错误。如果没有错误,
else
子句将执行,并且通过不在try
块中执行该代码,可以避免捕获意外错误。同样,捕捉一个意外错误可以隐藏错误。示例
例如:
“try,except”套件有两个可选子句,
else
和finally
。所以实际上是try-except-else-finally
。只有当
try
块没有异常时,else
才会求值。它允许我们简化下面更复杂的代码:因此,如果我们将一个
else
与另一个(可能会产生错误)进行比较,我们会发现它减少了代码行,并且我们可以拥有一个更可读、可维护和更少错误的代码库。finally
不管怎样,
finally
都将执行,即使正在使用return语句计算另一行。用伪码分解
它可能有助于打破这一点,在最小的可能形式,演示所有的功能,与评论。假设此语法正确(但除非定义了名称,否则无法运行)伪代码位于函数中。
例如:
的确,我们可以将代码包含在
else
块中的try
块中,如果没有异常,它将在那里运行,但是如果代码本身引发了我们捕获的那种异常呢?把它留在try
块中会隐藏这个bug。我们希望最小化
try
块中的代码行,以避免捕获我们没有预料到的异常,原则是如果代码失败,我们希望它大声失败。这是一个best practice。在Python中,大多数异常都是错误。
我们可以使用pydoc查看异常层次结构。例如,在Python 2中:
或者Python 3:
会给我们等级制度。我们可以看到,大多数类型的
Exception
都是错误,尽管Python使用其中一些错误来结束for
循环(StopIteration
)。这是Python3的层次结构:一位评论者问道:
不,您不返回异常,只需使用一个空的
raise
重新访问它以保留stacktrace。或者,在Python 3中,您可以引发一个新的异常,并使用异常链接保留回溯:
我在my answer here中详细说明。
在Python世界中,使用异常进行流控制是常见和正常的。
甚至Python核心开发人员也使用异常进行流控制,这种风格被大量地融入到语言中(即迭代器协议使用StopIteration来表示循环终止)。
此外,try-except样式用于防止某些"look-before-you-leap"构造中固有的竞争条件。例如,测试os.path.exists会产生在使用时可能已过期的信息。同样,Queue.full返回可能过时的信息。在这些情况下,try-except-else style将生成更可靠的代码。
在其他一些语言中,这一规则反映了其图书馆所反映的文化规范。“规则”也部分基于这些语言的性能考虑。
Python的文化规范有些不同。在许多情况下,必须对控制流使用异常。此外,在Python中使用异常并不像在某些编译语言中那样减慢周围的代码和调用代码的速度(即CPython已经在每个步骤实现了用于异常检查的代码,而不管您是否实际使用异常)。
换言之,您理解的“异常是针对异常的”这一规则在某些其他语言中是有意义的,但对Python却没有意义。
除了有助于避免争用条件外,异常对于拉取外部循环的错误处理也非常有用。这是解释语言中的一个必要的优化,解释语言通常没有自动的loop invariant code motion。
此外,在处理问题的能力与出现问题的地方相去甚远的常见情况下,异常可以大大简化代码。例如,通常有顶级用户界面代码来调用业务逻辑的代码,而业务逻辑反过来又调用低级例程。低级例程中出现的情况(例如数据库访问中唯一键的重复记录)只能在顶级代码中处理(例如要求用户提供与现有键不冲突的新键)。对这种控制流使用异常允许中级例程完全忽略这个问题,并与流控制的这一方面很好地分离。
有一个nice blog post on the indispensibility of exceptions here。
另外,请参阅堆栈溢出答案:Are exceptions really for exceptional errors?
else子句本身很有趣。它在没有异常的情况下运行,但在finally子句之前。这是它的主要目的。
如果没有else子句,在完成之前运行附加代码的唯一选项是将代码添加到try子句中的笨拙做法。那很笨拙,因为它有风险 在不受try块保护的代码中引发异常。
在完成之前运行其他未受保护的代码的用例并不经常出现。所以,不要期望在已发布的代码中看到许多示例。这有点罕见。
else子句的另一个用例是执行在没有发生异常时必须发生的操作,以及在处理异常时不发生的操作。例如:
unittest运行程序中还有一个例子:
最后,在try块中最常用的else子句是为了美化(将异常结果和非异常结果在相同的缩进级别上对齐)。这种用法总是可选的,并不是绝对必要的。
Python不同意异常只应用于异常情况的想法,事实上这个习惯用法是'ask for forgiveness, not permission'。这意味着,使用异常作为流控制的常规部分是完全可以接受的,而且事实上是受鼓励的。
这通常是一件好事,因为这样做有助于避免一些问题(作为一个明显的例子,通常可以避免争用条件),而且它往往会使代码更具可读性。
假设有这样一种情况,您接受一些需要处理的用户输入,但有一个已经处理的默认输入。
try: ... except: ... else: ...
结构使得代码非常可读:与其他语言相比:
注意优点。不需要检查该值是否有效并将其单独解析,它们只需执行一次。代码也遵循一个更符合逻辑的过程,主代码路径是第一个,然后是“如果它不起作用,就这样做”。
这个例子自然有点做作,但它显示了这种结构的一些情况。
相关问题 更多 >
编程相关推荐