错误和异常
到目前为止,错误消息还没有被提及,但是如果已经尝试了这些示例,你可能已经看到了一些。至少有两种明显不同的错误:语法错误和异常。
语法错误
语法错误,也称为解析错误,可能是你在学习 Python 时遇到的最常见的抱怨:
while True print('Hello world')
运行结果:
C:\Python310>python 2.pyFile "C:\Python310\2.py", line 1while True print('Hello world')^^^^^^^^^^^^^^^^^^^^^^^^^SyntaxError: invalid syntax. Perhaps you forgot a comma?
解析器重写出现问题的行,并显示一个小“箭头”,指向行中检测到错误的最早点。该错误是由箭头前面的标记引起的(或至少在该标记处检测到):在本例中,该错误是在函数 print() 处检测到的,因为它前面缺少冒号(':')。错误信息会输出文件名和行号,以便在输入来自脚本的情况下知道在哪里查找。
异常
即使一个语句或表达式在语法上是正确的,当试图执行它时也可能会引发错误。在执行过程中检测到的错误称为异常,并且是在一定条件下产生的:你将很快学会如何在 Python 程序中处理它们。但是,大多数异常在程序中都不会被处理,这会导致错误消息,如下所示:
10 * (1/0)
运行结果:
C:\Python310>python 2.pyTraceback (most recent call last):File "C:\Python310\2.py", line 1, in <module>10 * (1/0)ZeroDivisionError: division by zero
4 + spam*3
运行结果:
C:\Python310>python 2.pyTraceback (most recent call last):File "C:\Python310\2.py", line 1, in <module>4 + spam*3NameError: name 'spam' is not defined
'2' + 2
运行结果:
C:\Python310>python 2.pyTraceback (most recent call last):File "C:\Python310\2.py", line 1, in <module>'2' + 2TypeError: can only concatenate str (not "int") to str
错误消息的最后一行说明发生了什么。异常有不同的类型,类型会作为消息的一部分输出:示例中的类型有 ZeroDivisionError、NameError 和 TypeError。作为异常类型输出的字符串是发生的内置异常的名称。这一点适用于所有内置异常,但不一定适用于用户定义的异常(尽管这是一个有用的约定)。标准异常名称是内置标识符(不是保留关键字)。
该行的其余部分根据异常的类型和原因提供详细信息。
错误消息的前面部分以堆栈回溯的形式显示异常发生的上下文。通常,它包含一个堆栈回溯,列出源代码行;但是,它不会显示从标准输入读取的行。
处理异常
可以编写程序处理特定异常。看看下面的例子,它要求用户输入一个有效的整数,但允许用户中断程序(使用Control-C或操作系统支持的其他方式);请注意,用户生成的中断通过引发 KeyboardInterrupt 异常发出信号。
while True:try:x = int(input("请输入数字: "))breakexcept ValueError:print("不是有效数字,请再次尝试...")
try 语句的工作原理如下。
● 首先,执行 try 子句(try 和 except 关键字之间的语句)。
● 如果未发生异常,则跳过 except 子句并完成 try 语句的执行。
● 如果在执行 try 子句期间发生异常,则跳过该子句的其余部分。然后,如果其类型与 except 关键字命名的异常匹配,则执行 except 子句,然后继续执行 try 之后的语句。
● 如果发生与 except 子句中指定的异常不匹配的异常,则将其传递给外部 try 语句;如果未找到处理程序,则它是一个未处理的异常,执行将停止,并显示一条消息,如上所示。
try 语句可以有多个 except 子句,用于为不同的异常指定处理程序。最多将执行一个处理程序。处理程序只处理相应 try 子句中出现的异常,而不处理其他 try 语句的处理程序中出现的异常。except 子句可以将多个异常指定在带括号的元组中,例如:
except (RuntimeError, TypeError, NameError):pass
如果 except 子句中的类是同一个类或其基类,则该类与异常兼容(但相反,列出派生类的 except 子句与基类不兼容)。例如,以下代码将按该顺序打印 B、C、D:
class B(Exception):passclass C(B):passclass D(C):passfor cls in [B, C, D]:try:raise cls()except D:print("D")except C:print("C")except B:print("B")
请注意,如果 except 子句被颠倒(首先是 except B),它将打印 B、B、B,即触发第一个匹配的 except 子句。
最后一个 except 子句可以省略异常名称,用作通配符。使用时要格外小心,因为这样很容易掩盖真正的编程错误!它还可用于打印错误消息,然后重新引发异常(也允许调用方处理异常):
import systry:f = open('1.txt')s = f.readline()i = int(s.strip())except OSError as err:print("OS 错误: {0}".format(err))except ValueError:print("无法将数据转换为整数。")except:print("意外错误:", sys.exc_info()[0])raise
try … except 语句有一个可选的 else 子句,当该子句出现时,必须在所有 except 子句之后。如果 try 子句没有引发异常,必须执行某些代码,这种情况下就非常有用。例如:
创建一个 2.txt 文件:
{"1": "吕布", "2" :"赵云"}
import sysfor arg in sys.argv[1:]:try:f = open(arg, 'r')except OSError:print('不能打开', arg)else:print(arg, '有', len(f.readlines()), '行')f.close()
调用程序:
C:\Python310>python 2.py 2.txt
运行结果:
2.txt 有 1 行
使用 else 子句比向 try 子句添加其他代码要好,因为它可以避免意外捕获不是由 try … except 语句保护的代码引发的异常。
当异常发生时,它可能有一个关联的值,也称为异常参数。参数的展现和类型取决于异常类型。
except 子句可以在异常名称后指定变量。变量绑定到异常实例,其参数存储在 instance.args。为方便起见,异常实例定义了 __str__() 以便可以直接打印参数,而无需引用 .args。也可以在引发异常之前先实例化异常,并根据需要向其添加任何属性。
try:raise Exception('书名', '作者')except Exception as inst:print(type(inst)) # 异常实例print(inst.args) # 参数存储在 .argsprint(inst) # __str__ 允许 args 被直接输出,# 可也可能在异常的子类中被重写x, y = inst.args # 解包 argsprint('x =', x)print('y =', y)
运行结果:
<class 'Exception'>('书名', '作者')('书名', '作者')x = 书名y = 作者
如果异常有参数,它们将作为未处理异常消息的最后一部分(“详细信息”)打印。
异常处理程序不仅处理在 try 子句中立即发生的异常,还处理在 try 子句中调用(甚至间接调用)的函数中发生的异常。例如:
def fails():x = 1/0try:fails()except ZeroDivisionError as err:print('处理运行时错误:', err)
运行结果:
处理运行时错误: division by zero
官方文档:
https://docs.python.org/3.9/tutorial/errors.html




