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

Jua*_*ano 400 python exception-handling exception try-catch

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

try:
   try_this(whatever)
except SomeException as exception:
   #Handle exception
else:
   return something
Run Code Online (Sandbox Code Playgroud)

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

我不喜欢那种编程,因为它使用异常来执行流控制.但是,如果它包含在语言中,那么必须有充分的理由,不是吗?

我的理解是,异常不是错误,它们只应用于特殊情况(例如我尝试将文件写入磁盘,没有更多空间,或者我没有权限),而不是流程控制.

通常我将异常处理为:

something = some_default_value
try:
    something = try_this(whatever)
except SomeException as exception:
    #Handle exception
finally:
    return something
Run Code Online (Sandbox Code Playgroud)

或者如果我真的不想在发生异常时返回任何内容,那么:

try:
    something = try_this(whatever)
    return something
except SomeException as exception:
    #Handle exception
Run Code Online (Sandbox Code Playgroud)

Ray*_*ger 623

"我不知道这是不是出于无知,但我不喜欢那种编程,因为它使用异常来执行流控制."

在Python世界中,使用流控制的异常是常见且正常的.

甚至Python核心开发人员也使用流控制的异常,并且该样式在语言中被大量使用(即迭代器协议使用StopIteration来指示循环终止).

此外,try-except-style用于防止某些"先行一瞥"结构中固有的竞争条件.例如,测试os.path.exists会导致在您使用它时可能已过时的信息.同样,Queue.full返回可能过时的信息.在这些情况下,try-except-else样式将生成更可靠的代码.

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

在其他一些语言中,该规则反映了其图书馆反映的文化规范."规则"也部分基于这些语言的性能考虑因素.

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

换句话说,您对"异常是异常"的理解是一种在其他语言中有意义的规则,但对于Python则不然.

"但是,如果它包含在语言本身中,那么它必须有充分的理由,不是吗?"

除了帮助避免竞争条件之外,异常对于拉出外部循环的错误处理也非常有用.这是解释语言中的必要优化,其不倾向于具有自动循环不变代码运动.

此外,异常可以在常见情况下简化代码,在这种情况下,处理问题的能力远离问题出现的地方.例如,通常具有用于业务逻辑的顶级用户界面代码调用代码,该代码又调用低级例程.低级例程中出现的情况(例如数据库访问中唯一键的重复记录)只能在顶级代码中处理(例如要求用户提供与现有密钥不冲突的新密钥).对这种控制流使用异常允许中级例程完全忽略该问题,并且与流控制的这个方面很好地分离.

这里有关于例外不可或缺的好文章.

另外,请参阅此Stack Overflow回答:异常是否真的出现异常错误?

"尝试 - 除了 - 其他存在的原因是什么?"

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

如果没有else子句,在完成之前运行其他代码的唯一选择就是将代码添加到try-clause中的笨拙做法.这是笨拙的,因为它有可能在代码中引发异常,而这些异常并不是由try-block保护的.

在最终确定之前运行其他未受保护的代码的用例不会经常出现.因此,不要期望在已发布的代码中看到许多示例.这有点罕见.

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

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

最后,try-block中else子句的最常见用法是进行一些美化(在同一级别的缩进处调整异常结果和非异常结果).此用法始终是可选的,并非绝对必要.

  • "这很笨拙,因为它有可能在代码中引发异常,而这些异常并不是试图阻止的." 这是这里最重要的学习方式 (19认同)
  • 恕我直言,亚当的评论与现实不符。每次使用 for 循环时,StopIteration 异常都会用于控制流。当您在循环中运行 index() Sequence 方法时,您会捕获 ValueError 以结束循环。每当您使用推荐的 EAPF 样式来实现原子性时,您就在使用控制流的异常。它已深深融入到语言中。实际上,唯一不鼓励的是在有更简单的替代方案(例如“break”或“return”)时使用异常。 (5认同)
  • “在 Python 世界中,使用异常进行流量控制是常见且正常的。”——我认为有必要区分“Python 世界”和“CPython 核心开发世界”。我研究过很多 Python 代码库,很少看到用于流控制的异常,并且看到许多 Python 开发人员不鼓励这种使用。 (2认同)
  • “else 子句的另一个用例是执行在没有异常发生时必须发生的操作,以及在处理异常时不会发生的操作。” 这对我帮助很大。谢谢! (2认同)

Aar*_*all 161

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

一个try块可以处理预期的错误.该except块应该只捕获您准备处理的异常.如果您处理意外错误,您的代码可能会做错事并隐藏错误.

else,如果没有错误子句就会执行,并通过不执行的代码try块,避免受凉意外的错误.再次,捕获意外错误可以隐藏错误.

例如:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
else:
    return something
Run Code Online (Sandbox Code Playgroud)

"try,except"套件有两个可选子句,elsefinally.实际上就是这样try-except-else-finally.

else只有在try块中没有异常时才会进行评估.它允许我们简化下面更复杂的代码:

no_error = None
try:
    try_this(whatever)
    no_error = True
except SomeException as the_exception:
    handle(the_exception)
if no_error:
    return something
Run Code Online (Sandbox Code Playgroud)

因此,如果我们将a else与替代(可能会产生错误)进行比较,我们会发现它减少了代码行,并且我们可以拥有更易读,可维护且更少错误的代码库.

finally

finally 无论如何都会执行,即使用return语句评估另一行.

用伪代码分解

通过注释以尽可能最小的形式展示所有功能,可能有助于解决这个问题.假设这在语法上是正确的(但除非定义了名称,否则不可运行)伪代码在函数中.

例如:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle_SomeException(the_exception)
    # Handle a instance of SomeException or a subclass of it.
except Exception as the_exception:
    generic_handle(the_exception)
    # Handle any other exception that inherits from Exception
    # - doesn't include GeneratorExit, KeyboardInterrupt, SystemExit
    # Avoid bare `except:`
else: # there was no exception whatsoever
    return something()
    # if no exception, the "something()" gets evaluated,
    # but the return will not be executed due to the return in the
    # finally block below.
finally:
    # this block will execute no matter what, even if no exception,
    # after "something" is eval'd but before that value is returned
    # but even if there is an exception.
    # a return here will hijack the return functionality. e.g.:
    return True # hijacks the return in the else clause above
Run Code Online (Sandbox Code Playgroud)

确实,我们可以将代码包含在else块中的try块中,如果没有异常,它将在那里运行,但是如果代码本身引发了我们正在捕获的类型的异常呢?将它留在try块中会隐藏该bug.

我们希望最小化try块中的代码行以避免捕获我们没有预料到的异常,原则是如果我们的代码失败,我们希望它大声失败.这是最佳做法.

我的理解是异常不是错误

在Python中,大多数例外都是错误.

我们可以使用pydoc查看异常层次结构.例如,在Python 2中:

$ python -m pydoc exceptions
Run Code Online (Sandbox Code Playgroud)

或Python 3:

$ python -m pydoc builtins
Run Code Online (Sandbox Code Playgroud)

会给我们层次结构.我们可以看到大多数类型Exception都是错误,尽管Python使用其中的一些来结束for循环(StopIteration).这是Python 3的层次结构:

BaseException
    Exception
        ArithmeticError
            FloatingPointError
            OverflowError
            ZeroDivisionError
        AssertionError
        AttributeError
        BufferError
        EOFError
        ImportError
            ModuleNotFoundError
        LookupError
            IndexError
            KeyError
        MemoryError
        NameError
            UnboundLocalError
        OSError
            BlockingIOError
            ChildProcessError
            ConnectionError
                BrokenPipeError
                ConnectionAbortedError
                ConnectionRefusedError
                ConnectionResetError
            FileExistsError
            FileNotFoundError
            InterruptedError
            IsADirectoryError
            NotADirectoryError
            PermissionError
            ProcessLookupError
            TimeoutError
        ReferenceError
        RuntimeError
            NotImplementedError
            RecursionError
        StopAsyncIteration
        StopIteration
        SyntaxError
            IndentationError
                TabError
        SystemError
        TypeError
        ValueError
            UnicodeError
                UnicodeDecodeError
                UnicodeEncodeError
                UnicodeTranslateError
        Warning
            BytesWarning
            DeprecationWarning
            FutureWarning
            ImportWarning
            PendingDeprecationWarning
            ResourceWarning
            RuntimeWarning
            SyntaxWarning
            UnicodeWarning
            UserWarning
    GeneratorExit
    KeyboardInterrupt
    SystemExit
Run Code Online (Sandbox Code Playgroud)

一位评论者问:

假设您有一个ping外部API的方法,并且您希望在API包装器之外的类中处理异常,您是否只是从except子句下的方法返回e,其中e是异常对象?

不,你不会返回异常,只需用裸机重新加载它就raise可以保留堆栈跟踪.

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise
Run Code Online (Sandbox Code Playgroud)

或者,在Python 3中,您可以引发新的异常并使用异常链接保留回溯:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise DifferentException from the_exception
Run Code Online (Sandbox Code Playgroud)

我在这里详细说明了答案.


Gar*_*tty 33

Python并不认为异常只应用于例外情况,实际上成语是"请求宽恕,而不是许可".这意味着使用异常作为流量控制的常规部分是完全可以接受的,事实上,鼓励.

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

想象一下,你有一种情况,你需要处理一些用户输入,但有一个已经处理过的默认值.该try: ... except: ... else: ...结构使代码非常易读:

try:
   raw_value = int(input())
except ValueError:
   value = some_processed_value
else: # no error occured
   value = process_value(raw_value)
Run Code Online (Sandbox Code Playgroud)

与它在其他语言中的工作方式相比:

raw_value = input()
if valid_number(raw_value):
    value = process_value(int(raw_value))
else:
    value = some_processed_value
Run Code Online (Sandbox Code Playgroud)

注意优点.无需检查值是否有效并单独解析,它们只执行一次.代码也遵循更合乎逻辑的进展,首先是主代码路径,然后是'如果它不起作用,那么'.

这个例子自然有点人为,但它表明这种结构存在一些情况.


Aar*_*all 15

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

对此的答案是它依赖于上下文.如果你这样做:

d = dict()
try:
    item = d['item']
except KeyError:
    item = 'default'
Run Code Online (Sandbox Code Playgroud)

它表明你不太了解Python.此功能封装在dict.get方法中:

item = d.get('item', 'default')
Run Code Online (Sandbox Code Playgroud)

try/ except块是写什么都可以有效地在一行用原子方法执行的一个更加视觉混乱和冗长的方式.还有其他情况这是真的.

但是,这并不意味着我们应该避免所有异常处理.在某些情况下,最好避免竞争条件.不检查文件是否存在,只是尝试打开它,并捕获相应的IOError.为了简单起见和可读性,请尝试将其封装或将其视为适当的因素.

阅读Python禅宗,了解有些原则处于紧张状态,并且对过于依赖其中任何一个陈述的教条持谨慎态度.


Alg*_*bra 10

请参阅以下示例,其中说明了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.")
Run Code Online (Sandbox Code Playgroud)

实施它并来:

    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.
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很棒的简单示例,_quickly_演示了完整的** try **子句,而无需某人(可能很急)阅读冗长的抽象说明。(当然,当他们不再着急时,他们应该回来阅读完整的摘要。) (3认同)

hwj*_*wjp 8

仅仅因为没有其他人发表过这一观点,我会说

避免else使用 in 的子句,try/excepts 因为大多数人不熟悉它们

与关键字tryexcept、 and不同finally,该子句的含义else不是不言自明的;它的可读性较差。因为它不经常使用,所以它会导致阅读您代码的人想要仔细检查文档以确保他们了解发生了什么。

(我写这个答案正是因为我try/except/else在我的代码库中找到了 a,它引起了一个 wtf 时刻并迫使我进行一些谷歌搜索)。

所以,无论我在哪里看到像OP示例这样的代码:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
else:
    # do some more processing in non-exception case
    return something
Run Code Online (Sandbox Code Playgroud)

我更愿意重构为

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    return  # <1>
# do some more processing in non-exception case  <2>
return something
Run Code Online (Sandbox Code Playgroud)
  • <1>显式返回,清楚地表明,在异常情况下,我们已经完成工作

  • <2> 作为一个不错的小副作用,块中的代码else缩进了一级。

  • 魔鬼论反驳:使用它的人越多,它就会变得越受欢迎。仅供思考,尽管我确实同意可读性很重要。也就是说,一旦有人理解了 try-else,我会认为在许多情况下它比替代方案更具可读性。 (9认同)
  • 我很少相信任何根据“大多数人”可能/可能不知道的事情来事后批评自己的论点。我也对迎合无知的论点高度怀疑。`try`-`else` 是 C/C++/Java 中未找到的 Python 特定结构。对于“大多数人”的一种特定构造来说,大多数人将 Python 编程为 C/C++/Java,没有利用许多使 Python 优雅而强大的独特功能。因此我们应该使用一个笨拙/低效的 Python 子集来满足他们的需求?列表推导式似乎也引起了这种争论。我不买。 (4认同)

小智 7

每当你看到这个:

try:
    y = 1 / x
except ZeroDivisionError:
    pass
else:
    return y
Run Code Online (Sandbox Code Playgroud)

甚至这个:

try:
    return 1 / x
except ZeroDivisionError:
    return None
Run Code Online (Sandbox Code Playgroud)

考虑一下:

import contextlib
with contextlib.suppress(ZeroDivisionError):
    return 1 / x
Run Code Online (Sandbox Code Playgroud)

  • 它没有回答我的问题,因为那只是我朋友的一个例子。 (2认同)

Gre*_*reg 6

您应该谨慎使用finally块,因为它与try中使用else块的功能不同,除了。无论try的结果如何,都将运行finally块。

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

In [11]: try:
   ....:     dict_["b"]
   ....: except KeyError:
   ....:     pass
   ....: finally:
   ....:     print "something"
   ....:     
something
Run Code Online (Sandbox Code Playgroud)

正如每个人都指出的那样,使用else块会使您的代码更具可读性,并且仅在未引发异常时运行

In [14]: try:
             dict_["b"]
         except KeyError:
             pass
         else:
             print "something"
   ....:
Run Code Online (Sandbox Code Playgroud)