后悔没早点知道 Python 异常的这 9 个事

教育   2024-09-26 11:31   四川  


1)异常层次

Python 有许多内置异常,我们可能时不时会遇到,例如ZeroDivisionError、KeyError、ValueError、TypeError等等。

每个异常都是异常层次结构的一部分 -- 这意味着大多数异常都以某种方式从同一个父类Exception继承。

我们可以通过._subclasses_()打印某些异常类的子类来查看这一点。

另一种方法是使用.__bases__检查异常的父类

这可能很麻烦,所以我创建了一个函数来帮助你自动执行此操作:

一些例子:

2)BaseException 与 Exception

上面我们看到了ExceptionBaseException 并且Exception继承自其父类BaseException

那么有什么区别呢?

  • Exception是我们在常规编码中遇到的最常见异常的父类,例如ZeroDivision、ValueError、TypeError、KeyError
  • BaseException用来和Exception区别,其他继承自BaseException的异常一般用于特殊情况
  • BaseException的一些子类包括KeyboardInterrupt,SystemExit等

当我们创建自定义异常时,我们几乎应该从Exception而不是 BaseException 继承,因为Exception意味着它是由于常规编码错误或问题导致的正常错误。

另一方面,BaseException包含其他特殊异常,如KeyboardInterruptSystemExit,当我们想要退出 Python 程序时会强制引发这些异常。

假设我们有一个写得很糟糕的 while 循环,要求用户输入:

如果我们希望退出这个 while 循环,我们可以按 Control-C 来强制KeyboardInterrupt结束我们的 Python 程序。

但是如果我们将 BaseException 作为 e 除外,就会发生一些奇怪的事情。

我们现在无法使用 Control-C 强制KeyboardInterrupt结束我们的程序——我们现在只能终止整个终端实例。

重要的是——排除 BaseException 是不好的做法,因为这可能会导致程序出现奇怪的副作用。

3)“except Exception as e”应该放在最后

假设每个 try 块有多个异常,并且每个异常块以某种方式处理不同的异常。

由于排序的原因,我们首先检查是否存在 ZeroDivisionError

  • 如果有,则执行该except块。
  • 如果没有,我们继续下一个区块
  • except Exception as e块捕获了之前未捕获的所有其他异常,因此它应该是最后一个

如果我们把except Exception 作为 e 放在第一位,它会处理所有异常,而其他except块将不会执行。

注意 — ZeroDivisionError是 Exception 的子类这就是为什么我们需要将它放在except Exception as e块之前。

4) except (Exception1, Exception2) as e

但是如果我们想以相同的方式处理一堆异常怎么办?我们是否必须为每个异常重复代码?例如:

我们不必重复那么多代码。

在上面的例子中,以相同方式处理异常的前 3 个except块可以这样压缩为一个except块:

这里,第一个except块捕获ZeroDivisionError、ValueErrorKeyError,并以相同的方式处理它们。

5)用消息断言

我们通常使用assert关键字来检查某个条件是否满足。

  • 如果条件为 True,则不发生任何事情
  • 如果条件为 False,则发生 AssertionError

这是一个简单的功能示例——我们要求用户输入,但唯一有效的输入是“A”或“B”。因此,我们使用断言语句来确保这一点。

如果我们输入除 A 或 B 之外的任何内容,我们都会得到 AssertionError:

但是你知道我们可以向 AssertionError 添加自定义消息吗?我们只需要在断言消息后面添加一个字符串(以逗号分隔)

当发生 AssertionError 时,还会显示断言消息。

6)如何忽略断言语句

有一种方法可以忽略所有现有的assert语句。那就是在运行 Python 脚本时使用 -O 标志。

假设我们有以下带有assert语句的 Python 脚本。我们使用命令python filename.py正常运行它

这里,我们只是得到一个 AssertionError 并且根本没有到达我们的打印语句。

但是,我们可以使用 -O 标志忽略assert语句。我们现在使用命令python -O filename.py运行此脚本

注意——这是大写的 O

当我们使用 -O 标志运行 Python 时,特殊变量*debug*变为 False(默认情况下通常为 True)。因此,所有assert语句都会被忽略。

如果我们有许多assert语句,但希望运行所有代码而没有任何assert语句触发 AssertionError,那么这个技巧很有用。

7)try except else 块

我们可能在学习 Python 基础知识时已经学习过 try-except-finally 块。但是您听说过 try-except-else-finally 块吗?

else块位于except块之后。

  • 如果发生异常,则except块运行,但else块不运行
  • 如果没有发生异常,则except块不会运行,但else块会运行。

我们强制执行 ZeroDivisionError。在这种情况下,由于except块运行,所以else块不会运行。无论如何, finally块始终会运行。

现在让我们让try块无错误地运行。现在没有发生任何异常,我们的except块不会运行。因此,我们的else块会运行。

8)finally 块可以在 return 语句之后运行

正常情况下,函数中return语句之后不会发生任何事情。一旦执行return语句,我们就会立即退出函数。

编写一个简单的函数来说明这一点:

这里,print('orange')没有运行,因为它发生在return语句之后。return语句运行后,我们的函数立即停止,并忽略之后发生的一切。

但使用try-except-finally 中的 finally块,我们可以绕过这个限制?

无论如何,finally块内的代码都会运行。即使在函数内的return语句之后也是如此。

这里,我们在 try 块中 print('apple') 并返回 1。但是即使在执行 return 语句之后,我们仍然会像在 finally 块中一样 print('orange')。

如果我们有无论如何都需要运行的代码,这很有用,例如关闭文件或关闭数据库连接(否则可能会导致内存泄漏和其他问题)

9)raise Exception1 from Exception2

在更复杂的应用程序中,我们可能希望引发一系列异常,而不是单个异常。这样,我们就能更好地追踪异常的确切来源和原因。

为此,我们可以使用语法raise Exception1 from Exception2。

下面是一个简单的例子:

这里,我们首先强制引发 ZeroDivisionError。

在我们的except块中,我们从之前引发的 ZeroDivisionError 中引发另一个 ValueError。这样,我们得到了由 ZeroDivisionError 引起的 ValueError。

为了查找和检查异常的原因,我们可以简单地使用.__cause__属性。


🏴‍☠️宝藏级🏴‍☠️ 原创公众号『数据STUDIO』内容超级硬核。公众号以Python为核心语言,垂直于数据科学领域,包括可戳👉 PythonMySQL数据分析数据可视化机器学习与数据挖掘爬虫 等,从入门到进阶!

长按👇关注- 数据STUDIO -设为星标,干货速递

数据STUDIO
点击领取《Python学习手册》,后台回复「福利」获取。『数据STUDIO』专注于数据科学原创文章分享,内容以 Python 为核心语言,涵盖机器学习、数据分析、可视化、MySQL等领域干货知识总结及实战项目。
 最新文章