除了在Python中使用try之外,这是一个好的实践吗?

2024-04-25 05:01:19 发布

您现在位置:Python中文网/ 问答频道 /正文

在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

Tags: 语言returnas编程exception原因thiselse
3条回答

What is the reason for the try-except-else to exist?

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

如果没有错误,else子句将执行,并且通过不在try块中执行该代码,可以避免捕获意外错误。同样,捕捉一个意外错误可以隐藏错误。

示例

例如:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
else:
    return something

“try,except”套件有两个可选子句,elsefinally。所以实际上是try-except-else-finally

只有当try块没有异常时,else才会求值。它允许我们简化下面更复杂的代码:

no_error = None
try:
    try_this(whatever)
    no_error = True
except SomeException as the_exception:
    handle(the_exception)
if no_error:
    return something

因此,如果我们将一个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

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

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

It is my understanding that exceptions are not errors

在Python中,大多数异常都是错误。

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

$ python -m pydoc exceptions

或者Python 3:

$ python -m pydoc builtins

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

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

一位评论者问道:

Say you have a method which pings an external API and you want to handle the exception at a class outside the API wrapper, do you simply return e from the method under the except clause where e is the exception object?

不,您不返回异常,只需使用一个空的raise重新访问它以保留stacktrace。

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise

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

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise DifferentException from the_exception

我在my answer here中详细说明。

"I do not know if it is out of ignorance, but I do not like that kind of programming, as it is using exceptions to perform flow control."

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

甚至Python核心开发人员也使用异常进行流控制,这种风格被大量地融入到语言中(即迭代器协议使用StopIteration来表示循环终止)。

此外,try-except样式用于防止某些"look-before-you-leap"构造中固有的竞争条件。例如,测试os.path.exists会产生在使用时可能已过期的信息。同样,Queue.full返回可能过时的信息。在这些情况下,try-except-else style将生成更可靠的代码。

"It my understanding that exceptions are not errors, they should only be used for exceptional conditions"

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

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

换言之,您理解的“异常是针对异常的”这一规则在某些其他语言中是有意义的,但对Python却没有意义。

"However, if it is included in the language itself, there must be a good reason for it, isn't it?"

除了有助于避免争用条件外,异常对于拉取外部循环的错误处理也非常有用。这是解释语言中的一个必要的优化,解释语言通常没有自动的loop invariant code motion

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

有一个nice blog post on the indispensibility of exceptions here

另外,请参阅堆栈溢出答案:Are exceptions really for exceptional errors?

"What is the reason for the try-except-else to exist?"

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')

unittest运行程序中还有一个例子:

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块中最常用的else子句是为了美化(将异常结果和非异常结果在相同的缩进级别上对齐)。这种用法总是可选的,并不是绝对必要的。

Python不同意异常只应用于异常情况的想法,事实上这个习惯用法是'ask for forgiveness, not permission'。这意味着,使用异常作为流控制的常规部分是完全可以接受的,而且事实上是受鼓励的。

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

假设有这样一种情况,您接受一些需要处理的用户输入,但有一个已经处理的默认输入。try: ... except: ... else: ...结构使得代码非常可读:

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

注意优点。不需要检查该值是否有效并将其单独解析,它们只需执行一次。代码也遵循一个更符合逻辑的过程,主代码路径是第一个,然后是“如果它不起作用,就这样做”。

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

相关问题 更多 >