在Python中,我不时地看到块:

try:
   try_this(whatever)
except SomeException as exception:
   #Handle exception
else:
   return something

try-except-else存在的原因是什么?

我不喜欢这种编程,因为它使用异常来执行流控制。然而,如果它被包含在语言中,一定有一个很好的理由,不是吗?

我的理解是,异常不是错误,它们只应该用于异常情况(例如,我试图将一个文件写入磁盘,但没有更多的空间,或者我可能没有权限),而不是用于流量控制。

通常我是这样处理异常的:

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

当前回答

Python不赞同异常只用于异常情况的想法,事实上,习惯用法是“请求原谅,而不是许可”。这意味着使用异常作为流控制的常规部分是完全可以接受的,实际上是被鼓励的。

这通常是一件好事,因为以这种方式工作有助于避免一些问题(一个明显的例子是,经常避免竞争条件),而且它往往使代码更具可读性。

假设您有这样一种情况,需要处理一些用户输入,但默认值已经处理完毕。尝试:……除了:…其他:…结构使得代码可读性非常强:

try:
   raw_value = int(input())
except ValueError:
   value = some_processed_value
else: # no error occured
   value = process_value(raw_value)

与它在其他语言中的工作方式进行比较:

raw_value = input()
if valid_number(raw_value):
    value = process_value(int(raw_value))
else:
    value = some_processed_value

注意它的优点。没有必要检查值是否有效并分别解析它,它们只执行一次。代码也遵循一个更有逻辑的顺序,主代码路径是第一个,然后是“如果它不起作用,就这样做”。

这个例子自然有点做作,但它显示了这种结构的一些情况。

其他回答

当你看到这个:

try:
    y = 1 / x
except ZeroDivisionError:
    pass
else:
    return y

甚至是这样:

try:
    return 1 / x
except ZeroDivisionError:
    return None

不妨考虑一下这个问题:

import contextlib
with contextlib.suppress(ZeroDivisionError):
    return 1 / x

你应该小心使用finally块,因为它与在try中使用else块不是一回事,除了。finally块将运行,而不管try的结果是什么。

In [10]: dict_ = {"a": 1}

In [11]: try:
   ....:     dict_["b"]
   ....: except KeyError:
   ....:     pass
   ....: finally:
   ....:     print "something"
   ....:     
something

正如每个人都注意到的那样,使用else块使您的代码更具可读性,并且仅在没有抛出异常时运行

In [14]: try:
             dict_["b"]
         except KeyError:
             pass
         else:
             print "something"
   ....:

请看下面的例子,它说明了try-except-else-finally的所有内容:

for i in range(3):
    try:
        y = 1 / i
    except ZeroDivisionError:
        print(f"\ti = {i}")
        print("\tError report: ZeroDivisionError")
    else:
        print(f"\ti = {i}")
        print(f"\tNo error report and y equals {y}")
    finally:
        print("Try block is run.")

实施它,得到:

    i = 0
    Error report: ZeroDivisionError
Try block is run.
    i = 1
    No error report and y equals 1.0
Try block is run.
    i = 2
    No error report and y equals 0.5
Try block is run.

“我不知道这是不是出于无知,但我不喜欢这样 这是一种编程,因为它使用异常来执行流控制。”

在Python世界中,使用异常进行流控制是很常见和正常的。

即使是Python的核心开发人员也会使用异常来进行流控制,并且这种风格已经深深地嵌入到语言中(即迭代器协议使用StopIteration来发出循环终止的信号)。

此外,try-except-样式用于防止某些“三思而后行”构造中固有的竞争条件。例如,测试os.path.exists所得到的信息在您使用它时可能已经过时。同样地,队列。完整返回可能过期的信息。在这些情况下,try-except-else样式将生成更可靠的代码。

“根据我的理解,异常不是错误,它们应该只是 用于特殊情况"

在其他一些语言中,这一规则反映了他们的文化规范,就像他们的图书馆所反映的那样。该“规则”部分也是基于这些语言的性能考虑。

Python文化规范有些不同。在许多情况下,您必须为控制流使用异常。此外,在Python中使用异常不会像在一些编译语言中那样减慢周围的代码和调用代码(即CPython已经在每一步实现了异常检查的代码,不管你是否实际使用异常)。

换句话说,您理解的“异常适用于异常”的规则在其他一些语言中是有意义的,但对Python则不然。

然而,如果它包含在语言本身,就必须有一个 这是很好的理由,不是吗?”

除了有助于避免竞态条件,异常对于在外部循环中进行错误处理也非常有用。在解释性语言中,这是一个必要的优化,因为解释性语言往往没有自动循环不变的代码运动。

Also, exceptions can simplify code quite a bit in common situations where the ability to handle an issue is far removed from where the issue arose. For example, it is common to have top level user-interface code calling code for business logic which in turn calls low-level routines. Situations arising in the low-level routines (such as duplicate records for unique keys in database accesses) can only be handled in top-level code (such as asking the user for a new key that doesn't conflict with existing keys). The use of exceptions for this kind of control-flow allows the mid-level routines to completely ignore the issue and be nicely decoupled from that aspect of flow-control.

这里有一篇关于例外的必要性的不错的博客文章。

另外,请参阅Stack Overflow的回答:异常真的是针对异常错误的吗?

“除了别的尝试存在的原因是什么?”

else子句本身很有趣。它在没有异常时运行,但在finally子句之前。这是它的主要目的。

如果没有else-子句,在结束之前运行额外代码的唯一选择将是将代码添加到try-子句的笨拙实践。这是笨拙的,因为它有风险 在不打算由try块保护的代码中引发异常。

在终结之前运行额外的不受保护代码的用例并不经常出现。因此,不要期望在已发布的代码中看到很多示例。这有点罕见。

else子句的另一个用例是执行在没有异常发生时必须发生的操作,而在处理异常时不会发生的操作。例如:

recip = float('Inf')
try:
    recip = 1 / f(x)
except ZeroDivisionError:
    logging.info('Infinite result')
else:
    logging.info('Finite result')

另一个例子发生在单元测试运行器中:

try:
    tests_run += 1
    run_testcase(case)
except Exception:
    tests_failed += 1
    logging.exception('Failing test case: %r', case)
    print('F', end='')
else:
    logging.info('Successful test case: %r', case)
    print('.', end='')

最后,在try-block中使用else子句最常见的用途是进行一些美化(将异常结果和非异常结果对齐在同一缩进级别)。这种用法总是可选的,并不是严格必要的。

在python中使用try-except-else是一个好习惯吗?

答案是,这取决于上下文。如果你这样做:

d = dict()
try:
    item = d['item']
except KeyError:
    item = 'default'

这说明您不是很了解Python。这个功能封装在字典中。获得方法:

item = d.get('item', 'default')

try/except块是一种视觉上更加混乱和冗长的方式,它可以用原子方法在一行中有效地执行。在其他情况下也是如此。

然而,这并不意味着我们应该避免所有的异常处理。在某些情况下,最好避免竞争条件。不要检查文件是否存在,只是尝试打开它,并捕获相应的IOError。出于简单性和可读性的考虑,请尝试将其封装或适当地提取出来。

阅读Python的禅宗,了解有一些原则处于紧张状态,并警惕过分依赖其中任何一条陈述的教条。