发布于 2026-01-06 5 阅读
0

Dead Simple Python: Errors Playing Catch Reading Traceback Your Friend, the Exception Beware the Diaper Anti-Pattern Except, Else, Finally Being Exceptional Using The Exception Custom Exceptions Review

简单易懂的Python:错误

玩接球游戏

读取回溯

你的朋友,一个例外

警惕尿布反模式

除、否则、最后

卓越

使用异常

自定义例外

审查

喜欢这些文章吗?那就买本书吧! Jason C. McDonald 的《Dead Simple Python》由 No Starch Press 出版。


例外情况。

这是许多程序员的头号敌人之一。在许多编程语言中,我们习惯于将异常某种程度的失败联系起来;这意味着某个地方的某些东西被错误地使用了。

如果我告诉你,你不必害怕异常呢?它们其实是想成为你的朋友,帮助你编写更好的代码?

Python 提供了许多常见的错误处理工具,但它们的使用方式可能与你以往的习惯大相径庭,而且它的功能远不止于清理错误。你甚至可以说,Python 的错误处理机制内部结构更加复杂。

杰罗尼莫!

玩接球游戏

如果您对例外情况不太熟悉,我们先从一般定义开始……

异常:(计算机)正常处理过程中的中断,通常由错误情况引起,可以由程序的其他部分处理。(维基词典

我们先来看一个简单的例子:

def initiate_security_protocol(code):
    if code == 1:
        print("Returning onboard companion to home location...")
    if code == 712:
        print("Dematerializing to preset location...")

code = int(input("Enter security protocol code: "))
initiate_security_protocol(code)
Enter fullscreen mode Exit fullscreen mode
>>> Enter security protocol code: 712
Dematerializing to preset location...
>>> Enter security protocol code: seven one two
Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/security_protocols.py", line 7, in <module>
    code = int(input("Enter security protocol code: "))
ValueError: invalid literal for int() with base 10: 'seven one two'
Enter fullscreen mode Exit fullscreen mode

显然,这是个问题。我们不希望程序因为用户输入了一些奇怪的内容而突然崩溃。正如那句老话所说……

一位质量保证工程师走进一家酒吧。他点了一杯啤酒。他点了五杯啤酒。他点了负一杯啤酒。他点了一只蜥蜴。

我们需要防止出现异常输入。在这种情况下,只有一个主要的故障点:就是那个int()函数。它期望接收一个可以转换为整数的数据类型,如果接收不到,就会抛出异常。为了正确处理这种情况,我们将可能ValueError出错的代码封装在一个代码块中。try...except

try:
    code = int(input("Enter security protocol code: "))
except ValueError:
    code = 0
initiate_security_protocol(code)
Enter fullscreen mode Exit fullscreen mode

当我们再次测试代码时,就不会再出现那个错误了。如果我们无法从用户那里获取所需信息,我们会直接使用代码0代替。当然,我们可以重写initiate_security_protocol()函数来处理不同的代码0,不过为了节省时间,这里我就不赘述了。

陷阱提醒:不知何故,作为一名精通多种语言的程序员,我经常忘记except在 Python 中使用 `if` 语句,而不是像catch大多数其他语言那样使用 `if` 语句。这篇文章里我已经打错了三次(然后立刻就改正了)。这只是一个需要记忆的地方。好在 Python 没有 `if` 关键字,所以语法错误会更容易被发现。如果你也懂多种语言,遇到这种情况时不要慌张。正确的写法是 ` if`catch不是` exceptif` catch

读取回溯

在深入探讨该语句的细节之前try...except,让我们再回顾一下这个错误信息。毕竟,如果不讨论错误信息,一篇关于错误处理的文章又有什么意义呢?在 Python 中,我们称之为“回溯”(Traceback),因为它追溯了错误的源头,从第一行代码到最后一行代码。在许多其他语言中,这被称为“堆栈跟踪”(stack trace)。

Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/security_protocols.py", line 7, in <module>
    code = int(input("Enter security protocol code: "))
ValueError: invalid literal for int() with base 10: 'seven one two'
Enter fullscreen mode Exit fullscreen mode

我习惯从下往上阅读这些消息,因为这样能让我快速找到最重要的信息。如果你看最后一行,你会看到ValueError,这就是抛出的具体异常。具体细节如下:在这种情况下,无法使用将字符串转换'seven one two'为整数int()。我们还了解到,它试图转换为十进制整数,这在其他情况下可能很有用。例如,想象一下,如果那一行写的是……

ValueError: invalid literal for int() with base 10: '5bff'
Enter fullscreen mode Exit fullscreen mode

如果我们忘记指定十进制(base-16),比如int('5bff', 16)使用默认的十进制(base-10),就完全有可能发生这种情况。简而言之,你一定要仔细阅读并理解错误信息的最后一行!我曾经无数次因为只看了一眼错误信息,就浪费了半个小时去追查错误的bug,最后才发现只是漏掉了一个参数或者用错了函数。

错误信息上方是导致错误的代码行号(code = int(input("Enter security protocol code: ")))。再上方是文件的绝对路径(security_protocols.py)和行号7。该语句in <module>表示代码位于任何函数之外。在这个例子中,回调函数只有一个步骤,所以我们来看一个稍微复杂一些的例子。我已经修改并扩展了之前的代码。

Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 6, in <module>
    decode_message("Bad Wolf")
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 4, in decode_message
    initiate_security_protocol(message)
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/security_protocols.py", line 2, in initiate_security_protocol
    code = int(code)
ValueError: invalid literal for int() with base 10: 'Bad Wolf'
Enter fullscreen mode Exit fullscreen mode

我们遇到了和之前类似的错误——我们试图将字符串转换为整数,但失败了。倒数第二行显示了出错的代码;果然,就是那个引发错误的调用int()。根据上一行的说明,这段有问题的代码位于security_protocols.py`<string>` 函数内部的第 2 行initiate_security_protocol()。太好了!我们其实可以就此打住,把它包装在一个 `<string>` 中try...except。明白为什么从下往上阅读可以节省时间了吧?

然而,假设情况并非如此简单。也许我们没有修改模块的选项security_protocols.py,因此我们需要在模块执行之前就databank.py阻止问题发生。如果我们查看接下来的两行代码,会发现第4 行,在decode_message()函数内部,我们调用了initiate_security_protocol()出现问题的函数。而这个函数又在第 6 行被调用databank.py,此时它位于任何函数之外,而我们正是在这里将参数传递"Bad Wolf"给了它。

数据输入本身没有问题,因为我们想要解码消息“Bad Wolf”。但是,为什么要把我们正在尝试解码的消息直接传递给安全协议呢?或许我们需要重写那个函数(或者除了其他修改之外再重写一下?)。正如你所见,回溯信息对于理解错误根源至关重要。养成仔细阅读回溯信息的习惯;许多有用的信息可能隐藏在意想不到的地方。

顺便说一下,第一行每次都一样,但如果你忘记了如何阅读这些消息,它就非常有用。最近执行的代码列在最后。因此,正如我之前所说,你应该从下往上阅读它们。

你的朋友,一个例外

“事后请求原谅比事前获得许可容易得多。”——格蕾丝·霍珀海军少将

这句话最初是关于主动性的;如果你相信某个想法,就应该冒险尝试,而不是等待别人的许可才去实现它。然而,在这里,它完美地诠释了 Python 的错误处理理念:如果某个程序可能经常以一种或多种特定方式出错,那么通常最好使用try...except异常处理语句来处理这些情况。

这种理念的正式名称是“请求原谅比请求许可更容易”,或EAFP

这有点抽象,所以我们来看另一个例子。假设我们想在字典里查找信息。

datafile_index = {
    # Omitted for brevity.
    # Just assume there's a lot of data in here.
}

def get_datafile_id(subject):
    id = datafile_index[subject]
    print(f"See datafile {id}.")

get_datafile_id("Clara Oswald")
get_datafile_id("Ashildir")
Enter fullscreen mode Exit fullscreen mode
See datafile 6035215751266852927.

Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 30, in <module>
    get_datafile_id("Ashildir")
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/databank.py", line 26, in get_datafile_id
    id = datafile_index[subject]
KeyError: 'Ashildir'
Enter fullscreen mode Exit fullscreen mode

第一个函数调用运行正常。我们在字典中查找database_index键 `a` "Clara Oswald",该键存在,因此我们返回与其关联的值(`a` 6035215751266852927),并将该数据打印到我们精心格式化的print()语句中。然而,第二个函数调用失败了。KeyError抛出异常,因为 `a`"Ashildir"不是字典中的键。

技术说明: Pythoncollections.defaultdict为这个问题提供了另一种解决方案;尝试访问不存在的键时,会在字典中创建键值对,并使用默认值。但是,由于这是一个演示错误处理的示例,因此我没有使用它。

由于我们不可能合理地记住字典中的所有键,尤其是在实际应用中,因此我们需要一些方法来处理尝试访问不存在的键这种常见情况。你的第一反应可能是在尝试访问之前先检查字典中是否存在该键……

def get_datafile_id(subject):
    if subject in datafile_index:
        id = datafile_index[subject]
        print(f"See datafile {id}.")
    else:
        print(f"Datafile not found on {subject})
Enter fullscreen mode Exit fullscreen mode

在 Python 文化中,这种方法被称为“三思而后行”[LBYL]

但这并不是最有效的方法!这里就体现了“宽恕而非许可”的原则:我们不先进行测试,而是使用try...except……

def get_datafile_id(subject):
    try:
        id = datafile_index[subject]
        print(f"See datafile {id}.")
    except KeyError:
        print(f"Datafile not found on {subject}")
Enter fullscreen mode Exit fullscreen mode

其背后的逻辑很简单:我们只需访问一次密钥(即“权限”方法),而不是访问两次,并将实际异常作为逻辑分支的手段。

在 Python 中,我们并不认为异常是需要避免的。事实上,try...except异常是许多 Python 设计模式和算法的常规组成部分。不要害怕抛出和捕获异常!实际上,即使是键盘中断也是通过KeyboardInterrupt异常来处理的。

警告: try...except虽然警告功能强大,但并非万能。例如,None函数直接返回通常比抛出异常更好。只有当发生真正错误且最好由调用者处理时,才应该抛出异常。

警惕尿布反模式

每个Python开发者迟早都会发现这种方法有效:

try:
    someScaryFunction()
except:
    print("An error occured. Moving on!")
Enter fullscreen mode Exit fullscreen mode

裸块except允许你一次性捕获所有异常。在 Mike Pirnat 的著作《如何在 Python 中犯错》(O'Reilly,2018)中,他称之为“尿布模式” ,这真的是一个非常糟糕的做法。我让他来总结一下……

……所有关于实际错误的宝贵上下文信息都被困在了“尿布”里,永远无法重见天日,也无法出现在你的问题跟踪系统中。当稍后发生“崩溃”异常时,堆栈跟踪指向的是次要错误发生的位置,而不是 try 代码块内部的实际错误。

简而言之,你应该始终显式地捕获特定的异常类型。任何你无法预见的故障都可能与需要解决的某些错误有关;例如,当你极其复杂的搜索函数突然抛出异常OSError而不是预期的异常KeyErrorTypeError

正如往常一样,Python之禅对此也有话要说……

错误绝不应该默默无闻地通过,
除非明确地设置了静默模式。

换句话说,这又不是宝可梦——你不应该把它们全部抓走!

您可以在文章《最邪恶的 Python 反模式》中详细了解为什么尿布模式是一个如此糟糕的主意

除、否则、最后

太好了,这样我就不用一次性捕获所有异常了。那么,我该如何处理多个可能出现的失败情况呢?

你会很高兴地发现,Pythontry...except拥有的工具比它最初展现出来的要多得多。

class SonicScrewdriver:

    def __init__(self):
        self.memory = 0

    def perform_division(self, lhs, rhs):
        try:
            result = float(lhs)/float(rhs)
        except ZeroDivisionError:
            print("Wibbly wobbly, timey wimey.")
            result = "Infinity"
        except (ValueError, UnicodeError):
            print("Oy! Don't diss the sonic!")
            result = "Cannot Calculate"
        else:
            self.memory = result
        finally:
            print(f"Calculation Result: {result}\n")


sonic = SonicScrewdriver()

sonic.perform_division(8, 4)
sonic.perform_division(4, 0)
sonic.perform_division(4, "zero")

print(f"Memory Is: {sonic.memory}")
Enter fullscreen mode Exit fullscreen mode

在展示输出结果之前,请仔细阅读代码。你认为这三个sonic.perform_division()函数调用分别会输出什么?最终存储的是什么sonic.memory?看看你能不能找出答案。

你觉得自己答对了吗?让我们看看你是否答对了。

Calculation Result: 2.0

Wibbly wobbly, timey wimey.
Calculation Result: Infinity

Oy! Don't diss the sonic!
Calculation Result: Cannot Calculate

Memory Is: 2.0
Enter fullscreen mode Exit fullscreen mode

你感到惊讶吗?还是猜对了?让我们来分析一下。

try:当然,这就是我们尝试运行的代码,它可能会也可能不会引发异常。

except ZeroDivisionError:当我们尝试除以零时,就会发生这种情况。我们称该值为"Infinity"计算结果,并打印出一条关于时空连续体本质的恰当信息。

except (ValueError, UnicodeError):当引发以下两个异常之一时,就会发生此异常。ValueError当传递的任何参数无法通过 `std::float` 进行类型转换float()时,就会发生此异常;而UnicodeError当 Unicode 编码或解码出现问题时,就会发生此异常。实际上,第二个异常只是为了说明问题而添加的;ValueError对于所有参数无法转换为浮点数的合理场景,`std::float` 就足够了。无论哪种情况,我们都使用 `std::float` 的值"Cannot Calculate"作为结果,并提醒用户不要对硬件提出不合理的要求。

这里就变得有趣了。这段代码仅在没有抛出异常的情况下else:运行。在这种情况下,如果我们得到了一个有效的除法计算结果(数值),我们实际上希望将其存储在内存中;相反,如果结果是“无穷大”或“无法计算”,则不会存储

无论如何,finally:部分都会运行。在这种情况下,我们会打印出计算结果。

顺序很重要。我们必须遵循这个模式try...except...else...finallyelse如果存在,则必须放在所有except语句之后。finally始终放在最后。

一开始很容易混淆else两者finally,所以务必理解它们的区别。else仅在未引发异常时运行;finally每次都会运行。

最终版有多正式finally

你认为以下操作会起到什么作用?

class SonicScrewdriver:

    def __init__(self):
        self.memory = 0

    def perform_division(self, lhs, rhs):
        try:
            result = float(lhs)/float(rhs)
        except ZeroDivisionError:
            print("Wibbly wobbly, timey wimey.")
            result = "Infinity"
        except (ValueError, UnicodeError):
            print("Oy! Don't diss the sonic!")
            result = "Cannot Calculate"
        else:
            self.memory = result
            return result
        finally:
            print(f"Calculation Result: {result}\n")
            result = -1


sonic = SonicScrewdriver()

print(sonic.perform_division(8, 4))
Enter fullscreen mode Exit fullscreen mode

下面那return句话else应该就到此为止了吧?其实不然!如果我们运行那段代码……

Calculation Result: 2.0

2.0
Enter fullscreen mode Exit fullscreen mode

由此可以得出两个重要的结论:

  1. finally即使在我们return执行语句之后,该函数仍在运行。它没有像往常一样退出。

  2. return语句确实在代码块执行之前运行我们finally知道这一点是因为输出结果为 `null` 2.0,而不是我们在语句中-1赋值给 `null` 的值resultfinally

finally每次都会运行,即使结构return中其他地方也有try...except

os.abort()然而,我也用 `if` 语句代替 `if`语句进行了测试return result,在这种情况下,finally代码块根本没有执行;程序直接中止了。你可以在任何地方直接停止程序执行,Python 会放弃当前正在执行的操作并退出。即使出现这种异常finally行为,这条规则仍然不变。

卓越

所以,我们可以用它来捕获执行try...except。但如果我们真的想抛出一个执行呢?

在 Python 术语中,我们说我们抛出了一个异常,就像这门语言中的大多数事情一样,实现这一点显而易见:只需使用raise关键字即可:

class Tardis:

    def __init__(self):
        pass

    def camouflage(self):
        raise NotImplementedError('Chameleon circuits are stuck.')

tardis = Tardis()
tardis.camouflage()
Enter fullscreen mode Exit fullscreen mode

执行该代码时,我们会看到引发的异常。

Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/tardis.py", line 10, in <module>
    tardis.camoflague()
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/tardis.py", line 7, in camoflague
    raise NotImplementedError('Chameleon circuits are stuck.')
NotImplementedError: Chameleon circuits are stuck.
Enter fullscreen mode Exit fullscreen mode

唉,看来我们只能用那张警亭停车表格了。不过至少这样更容易记住停车位置。

注意:` NotImplementedErrorNone` 异常Python 内置异常之一,有时用于指示某个函数尚未完成(但将来会完成),因此不应立即使用。它与 `None`不可互换。请参阅文档以了解何时使用哪个异常。NotImplemented

关键代码显然是 ` raise NotImplementedError('Chameleon circuits are stuck.').`。在关键字之后raise,我们需要指定要引发的异常对象的名称。大多数情况下,我们会创建一个基于 `Exception` 类的新对象,正如您从括号的使用中看到的那样。所有异常都接受一个字符串作为第一个参数,用于指定异常消息。某些异常接受或需要更多参数,请参阅相关文档

使用异常

有时我们需要在捕获异常对其进行一些处理。有一些非常简单的方法可以做到这一点。

最直接的方法是打印异常信息。为此,我们需要能够操作捕获到的异常对象。让我们将语句更改except为`<exception_object> except NotImplementedError as e:`,其中e`<name>` 是我们绑定到异常对象的名称。然后,我们就可以直接使用 ` e<exception_object>` 作为对象了。

tardis = Tardis()

try:
    tardis.camouflage()
except NotImplementedError as e:
    print(e)
Enter fullscreen mode Exit fullscreen mode

异常类定义了__str__()返回异常消息的函数,所以如果我们将其强制转换为字符串str(),就能得到异常消息。你可能还记得之前的文章中提到过,`__init__` 函数会print()自动将其参数强制转换为字符串。当我们运行这段代码时,会得到……

Chameleon circuits are stuck.
Enter fullscreen mode Exit fullscreen mode

太好了,这很简单!

沸腾

那么,如果我们想再次抛出异常呢

等等,什么?我们才刚抓到它,为什么还要再提一遍?

例如,如果您需要在后台执行一些清理工作,但最终仍然希望调用者处理异常。以下是一个示例……

class Byzantium:

    def __init__(self):
        self.power = 0

    def gravity_field(self):
        if self.power <= 0:
        raise SystemError("Gravity Failing")


def grab_handle():
    pass


byzantium = Byzantium()

try:
    byzantium.gravity_field()
except SystemError:
    grab_handle()
    print("Night night")
    raise
Enter fullscreen mode Exit fullscreen mode

在上面的例子中,我们只是想抓住某个实体(grab_handle()),打印一条额外的消息,然后让异常继续抛出raise。当我们重新抛出异常时,我们称之为“冒泡”

Night night
Traceback (most recent call last):
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/byzantium.py", line 18, in <module>
    byzantium.gravity_field()
  File "/home/jason/Code/FiringRanges/PyFiringRange/Sandbox/byzantium.py", line 8, in gravity_field
    raise SystemError("Gravity Failing")
SystemError: Gravity Failing
Enter fullscreen mode Exit fullscreen mode

注意:你可能觉得我们需要加上except SystemError as e:`and`raise e或 `or` 之类的语句,但这完全是多余的。要让异常向上冒泡,我们需要raise单独调用 `this` 即可。

那么,如果我们想在抛出异常的同时添加一些额外信息呢?你可能首先想到的是抛出一个新的异常,但这会引入一些问题。为了演示这一点,我将在执行顺序中添加另一层。注意,当我处理该异常时SystemError,我抛出的是一个新的RuntimeError异常。我在第二个代码块中捕获了这个新的异常try...except

byzantium = Byzantium()

def test():
    try:
        byzantium.gravity_field()
    except SystemError:
        grab_handle()
        raise RuntimeError("Night night")

try:
    test()
except RuntimeError as e:
    print(e)
    print(e.__cause__)
Enter fullscreen mode Exit fullscreen mode

运行此程序后,我们将得到以下输出。

Night night
None
Enter fullscreen mode Exit fullscreen mode

当我们捕获到这个新异常时,我们完全不知道它是由什么引起的。为了解决这个问题,Python 3PEP 3134中引入了显式异常链。实现起来很简单。看看我们的新函数,这是我对上一个例子唯一做的改动。test()

byzantium = Byzantium()

def test():
    try:
        byzantium.gravity_field()
    except SystemError as e:
        grab_handle()
        raise RuntimeError("Night night") from e

try:
    test()
except RuntimeError as e:
    print(e)
    print(e.__cause__)
Enter fullscreen mode Exit fullscreen mode

你明白我在这里的操作了吗?在except语句中,我将名称绑定e到了我们之前捕获的异常。然后,在抛出新RuntimeError异常时,我将其与之前的异常链接起来from e。现在我们的输出是……

Night night
Gravity Failing
Enter fullscreen mode Exit fullscreen mode

运行该程序时,新抛出的异常会记住它的来源——之前的异常信息存储在它的__cause__属性中(输出的第二行会显示)。这对于日志记录尤其有用。

使用异常类还有许多其他技巧可以实现,尤其是在引入 PEP 3134 之后。和往常一样,我建议您阅读文档,我在文章末尾提供了链接。

自定义例外

Python 拥有大量的异常处理机制,并且它们的用法都有非常详细的文档说明。我经常参考这份异常处理列表来选择合适的异常处理程序。然而,有时候,我们需要一些更……个性化的异常处理方法。

所有错误类型的异常都派生自 `Exception` 类Exception,而 `Exception` 类又派生自 ` BaseExceptionException` 类。这种双重层次结构的目的是为了捕获所有错误,Exceptions而无需处理像 `Exception` 这样特殊的、非系统退出的异常KeyboardInterrupt。当然,这在实践中对你影响不大,因为 `Exception`except Exception实际上总是我之前提到的“尿布反模式”的另一种形式。无论如何,不​​建议你直接从 `Exception` 派生BaseException——只需知道它的存在即可。

创建自定义异常时,实际上可以继承任何你喜欢的异常类。有时,最好继承与你创建的异常用途最接近的异常类。但是,如果你不知道该选择哪个,也可以直接继承自 `Exception` 类Exception

咱们来做一个吧?

class SpacetimeError(Exception):
    def __init__(self, message):
        super().__init__(message)

class Tardis():

    def __init__(self):
        self._destination = ""
        self._timestream = []

    def cloister_bell(self):
        print("(Ominous bell tolling)")

    def dematerialize(self):
        self._timestream.append(self._destination)
        print("(Nifty whirring sound)")

    def set_destination(self, dest):
        if dest in self._timestream:
            self.cloister_bell()
        self._destination = dest

    def engage(self):
        if self._destination in self._timestream:
            raise SpacetimeError("You should not cross your own timestream!")
        else:
            self.dematerialize()


tardis = Tardis()

# Should be fine
tardis.set_destination("7775/349x10,012/acorn")
tardis.engage()

# Also fine
tardis.set_destination("5136/161x298,58/delta")
tardis.engage()

# The TARDIS is not going to like this...
tardis.set_destination("7775/349x10,012/acorn")
tardis.engage()
Enter fullscreen mode Exit fullscreen mode

显然,最后一个操作会导致我们提出的SpacetimeError异常被抛出。

我们再来看一下这个异常类声明。

class SpacetimeError(Exception):
    def __init__(self, message):
        super().__init__(message)
Enter fullscreen mode Exit fullscreen mode

其实写起来非常简单。如果你还记得我们之前对类的讲解,super().__init__()就会知道 `this` 是调用基类的初始化器,Exception在本例中就是 `this`。我们将传递给异常构造函数的消息SpacetimeError传递给基类的初始化器。

事实上,如果我所做的只是将参数传递messagesuper()类,我可以让它变得更简单:

class SpacetimeError(Exception):
    pass
Enter fullscreen mode Exit fullscreen mode

Python 本身就能处理基本操作。

这就是我们需要做的全部,当然,和往常一样,我们还可以利用它做更多的事情。自定义异常不仅仅是一个好听的名字;我们可以用它们来处理各种不寻常的错误情况,但这显然超出了本指南的范围。

审查

恭喜你顺利完成了我们对 Python 错误探索的讲解,没有被蒸发、删除、升级,或者误送到其他县,为你欢呼三声!让我们回顾一下要点:

  • 不要害怕Python中的异常!我们可以利用它们使我们的代码更加简洁。
  • 使用代码块捕获异常try...except。(是 `if` 语句except,不是`if` 语句catch!)
  • 永远不要使用尿布反模式,这只是一个简单的except:声明,或者(通常)是一个except Exception:
  • else代码块可以包含在最后一个代码块之后except,并且只有在代码块中的代码没有引发异常时才会运行try
  • finally代码块可以包含在语句的末尾try...except,并且总是会执行,即使我们处于OR代码块returnexceptelse
  • 我们通过抛出异常来“抛出”它,方法是通过raise WhateverError("Our message")
  • 在代码块内except,我们可以使用裸露的异常向上冒泡raise(重新引发) 。
  • 我们可以通过继承该类Exception或其众多子类之一来创建自定义异常类。

一如既往,文档中包含的信息远不止这些。我强烈建议大家查阅一下:


感谢deniskagrym(Freenode IRC #python) 提出的修改建议。

文章来源:https://dev.to/codemouse92/dead-simple-python-errors-l82