前言

在学习Python编程当中,我们经常要调试自己编写的代码,在调试/运行过程中,会经常看到一些报错信息。那么这些报错信息是怎么出现的呢?里面又涉及到什么的知识点呢?下面我们就来讲一讲。

在编程当中我们经常会遇到两种错误,分别是“语法错误” 和 "程序异常"。

什么是语法错误?

常见引起语法错误的原因,都是因为程序员在编写代码时没有按照约定的语法格式进行代码的编写,这就使得编译器无法进行解析,程序也因此终止执行,这也是初学者遇到最多的一个问题。

实例如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

while True
    prinf("Hello World!")

运行输出:

File "C:/Users/Qin/Desktop/python/test_3.py", line 4
while True
^
SyntaxError: invalid syntax

这个例子中,因为解析器在解释 while True 时,发现后面少了一个冒号 : ,并非是约定好的格式,所以编译器就会报错。

语法解释器在执行中发现有误后,就会指出错误的代码行,并且在引起错误的大致位置标记了一个小小的箭头。

什么是程序异常?

异常是指程序在执行中遇到的一个事件,该事件会在程序执行过程中突然发生,并影响了程序的正常执行。

一般情况下,程序发生异常都是因为 Python 解释器遇到了无法正常处理的代码语句,导致了程序中断,而产生一个异常。

我们在调试/运行代码的过程中,经常会遇到各种的错误(编译器报错),这些错误就是一个个不同的异常事件。

语法错误也算是程序异常中的一种情况。

有哪些异常情况?

Python 给出了各种各样的异常指示,具体如下:

序号异常名称描述
1BaseException所有异常的基类
2KeyboardInterrupt用户中断执行(通常是输入^C)
3Exception常规错误的基类
4StopIteration迭代器没有更多的值
5GeneratorExit生成器(generator)发生异常来通知退出
6StandardError所有的内建标准异常的基类
7ArithmeticError所有数值计算错误的基类
8FloatingPointError浮点计算错误
9OverflowError数值运算超出最大限制
10ZeroDivisionError除(或取模)零 (所有数据类型)
11AssertionError断言语句失败
12AttributeError对象没有这个属性
13EOFError没有内建输入,到达EOF 标记
14EnvironmentError操作系统错误的基类
15IOError输入/输出操作失败
16OSError操作系统错误
17WindowsError系统调用失败
18ImportError导入模块/对象失败
19LookupError无效数据查询的基类
20IndexError序列中没有此索引(index)
21KeyError映射中没有这个键
22MemoryError内存溢出错误(对于Python 解释器不是致命的)
23NameError未声明/初始化对象 (没有属性)
24UnboundLocalError访问未初始化的本地变量
25ReferenceError弱引用(Weak reference)试图访问已经垃圾回收了的对象
26RuntimeError一般的运行时错误
27NotImplementedError尚未实现的方法
28SyntaxErrorPython 语法错误
29IndentationError缩进错误
30TabErrorTab 和空格混用
31SystemError一般的解释器系统错误
32TypeError对类型无效的操作
33ValueError传入无效的参数
34UnicodeErrorUnicode 相关的错误
35UnicodeDecodeErrorUnicode 解码时的错误
36UnicodeEncodeErrorUnicode 编码时错误
37UnicodeTranslateErrorUnicode 转换时错误
38Warning警告的基类
39DeprecationWarning关于被弃用的特征的警告
40FutureWarning关于构造将来语义会有改变的警告
41OverflowWarning旧的关于自动提升为长整型(long)的警告
42PendingDeprecationWarning关于特性将会被废弃的警告
43RuntimeWarning可疑的运行时行为(runtime behavior)的警告
44SyntaxWarning可疑的语法的警告
45UserWarning用户代码生成的警告

异常处理

Python 提供了 异常处理断言(Assertions) 这两个功能来处理程序在运行中出现的异常和错误。

在 Python 执行过程中,如果发生了异常,我们需要捕获并处理它,否则程序将终止运行。

在进行异常处理之前,我们需要先捕获出现异常的信息,此时,我们可以使用 try...except... 语句来捕获相关异常的信息,后面再进行相关的处理。

基本语法:

try:
    <测试代码>     
except <异常名称_1>:
    <异常_1的处理代码>     
except <异常名称_2>:
    <异常_2的处理代码>    
else:
    <执行代码>

执行过程:
try 中通常存放的都是一些可能存在异常或需要测试代码运行情况的代码块。程序一开始先执行 try 中的内容,如果 try 中代码没有出现异常,则在执行完 try 中的语句后,转到 else 中继续执行,然后再接着往下执行代码。如果在 try 中发生了异常,程序就会自动捕获异常的相关情况,然后到 except 语句中进行异常情况比对,如果比对成功,则程序执行相应 except 语句中的内容,如果第一个 except 语句没有匹配上,程序将继续往下进行匹配,直至匹配完当前的所有 except 语句,最后如果都没有匹配到相应的异常情况,程序将向解析器抛出异常,程序执行终止。

说明:

1、其中 else 部分为可选项,如果存在,程序就会根据相关的情况做成正确的执行。

2、以上提到的是单级异常的执行过程,同样的,异常也可以进行嵌套。当最内层的代码发生异常时,而程序又没有在其同级的异常处理中找到相应的 except 情况,此时,内部的异常处理机构就会向上级进行提交,如果上级中有此异常处理的声明,则在上级进行处理。依次类推,直到在最外层的异常处理语句中都没有找到相关的 excpet 情况时,解析器将抛出最后的异常,然后终止程序运行。

实例演示1--(无异常):

说明:以" 可读可写 "方式打开文件,并向文件中写入内容,此时程序正常运行,即无异常。

#!/usr/bin/python
# -*- coding: UTF-8 -*-

try:
    fo = open("testfile.txt","w+")    # 以可读可写模式打开文件
    fo.write("这是异常测试实验,用于测试Python程序异常!")
except IOError:
    print( "主仁,程序发生异常了,可能是没有找到文件或读取文件失败~" )
else:
    print( "主仁,我已经成功将内容写入到文件当中啦!" )
    fo.close()

运行输出:

主仁,我已经成功将内容写入到文件当中啦!

此时,打开 testfile.txt 文件,可以发现内容已经成功写入到文件当中了。

实例演示2--(发生异常):

说明:以" 只读 "方式打开文件,此时,由于文件没有写入权限,此时程序会发生异常。这时就可以使用except语句捕获对应的IOError异常。

#!/usr/bin/python
# -*- coding: UTF-8 -*-

try:
    fo = open("testfile.txt","r")    # 以只读模式打开文件
    fo.write("这是异常测试实验,用于测试Python程序异常!")
except IOError:
    print( "主仁,程序发生异常了,可能是没有找到文件或读取文件失败~" )
else:
    print( "主仁,我已经成功将内容写入到文件当中啦!" )
    fo.close()

运行输出:

主仁,程序发生异常了,可能是没有找到文件或读取文件失败~

可见程序已经捕获到了异常,但程序并没有因此终止,而是继续往下执行了。

except 带多个异常类型

前面介绍了一个 except 语句捕获一种对应的异常类型,这次我们就来说说如何使用一个except语句捕获多种异常事件。

基本语法:

try:
    <测试代码>
except(Exception1[, Exception2[,...ExceptionN]]):
   <异常处理代码>
else:
    <执行代码>

参数说明:
Exception1:指异常信息(名称)。

此语法格式可以使用一个 except 来捕获多个异常信息,并进行处理。但此语法有个缺点就是,它没法办帮助我们区分程序到底是发生了指定异常信息里的哪一种异常。同样的,当异常信息区间里没有匹配的异常信息时,程序最终会向顶级进行报错,程序终止。

实例如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

try:
    a = 100     # 初始化一个变量
    print( "该变量已经被定义啦!" ,"a = " ,a )
    print( "看看 b 有没有被定义了.")
    print( b )       # 该变量没有定义,所以程序会报错
except(IOError ,NameError):
    print("程序发生异常啦,可能是变量没定义.")
else:
    print("程序没有发生异常,已经顺利执行到else这啦!")

print("异常已经处理完毕,程序还在继续运行...")

运行输出:

该变量已经被定义啦! a = 100
看看 b 有没有被定义了.
程序发生异常啦,可能是变量没定义.
异常已经处理完毕,程序还在继续运行...

在使用变量前需要先声明变量,这样编译器才不会报错。由于这里的 b 在调用前没有先声明,所以编译器会报 NameError 异常,此时使用 except 进行此异常的捕获并处理,所以当程序发生异常后,程序自动寻找对应的except处理方案进行异常的处理,处理完成后,程序继续往下执行。如果没有找到对应的处理方案,编译器将报错并终止执行。

如果把 except 中的 NameError 除掉,此时程序将终止,各位可以自行尝试,看一下具体的效果。

except 不带任何异常类型

有时我们需要让程序无论在任何情况下都不能停止,而前面介绍的方法都是针对指定的异常情况进行处理的,那么有没有别的办法可以做到这一点呢?这时,同样可以使用 try...except... 语句处理。要让它捕获并处理所有的异常,这时只需不给 except 附上任何的参数即可:

基本语法:

try:
    <测试代码>
except:
    <异常处理代码>
else:
    <执行代码>

以上语法可以捕获所有的异常信息,但我们不能通过该程序识别出具体的异常信息。 所以不在特定的情况下,我们一般都不使用该方法。

实例如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

try:
    print( a )
except:
    print("程序发生异常啦!")

实例输出:

程序发生异常啦!

try...finally... 语句

a ) finally... 条件也是 try... 语句中的一个可选择的选项。

b ) 该语句与不带参数的 try...except... 语句有一个共通点,那就是无论程序发生任何错误异常,程序都会执行finally内的语句。

c ) 使用本语句时,当程序发生异常后,编译器会报错,并将错误信息打印出来,提示用户程序发生了何种异常。但此时程序不会停止还是继续往下执行。

d ) try...finally... 语句比 try...except... 语句要优的一个地方就是,在可以捕获并处理任何异常的情况下,try...finally... 语句可以提示用户程序是发生了何种异常。

基本语法:

try:
   <测试代码>
finally:
   <异常处理代码>

实例如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

try:
    print( a )
finally:
    print("程序发生异常啦!")

运行输出:

Traceback (most recent call last):
File "C:/Users/QinTaiW/Desktop/python/test_3.py", line 5, in <module>
print( a )
NameError: name 'a' is not defined
程序发生异常啦!

可见程序在发生异常后,编译器提示用户程序发生了 NameError 的异常,然后就转到 finally 内部去执行相应的处理语句了。

自定义触发异常

如果需要自定义程序触发异常,我们可以使用raise语句来触发程序异常。

基本格式:

raise [Exception [, args [, traceback]]]

参数说明:

  • Exception : 标准异常的类型(如:IOError),它必须是一个异常的实例或者是异常的类(也就是 Exception 的子类)。
  • args : 是可选参数项,一般是用户自定义的异常参数。
  • traceback : 是可选参数项,如果存在,一般是跟踪异常对象。

演示实例 1:

自定义一个 不带参数 的触发异常,异常类型为 NameError 。代码如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

try:
    raise NameError     # 不带任何参数
except NameError:
        print("已经成功触发异常啦!")

运行输出:

已经成功触发异常啦!

演示实例 2:

自定义一个 带参数 的触发异常,异常类型为 NameError , 并查看异常提示信息。代码如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

try:
    raise NameError("这是自定义的异常提示信息")     # 附加上参数
except NameError:
        print("已经成功触发异常啦!")
        raise    # 使用raise 查看抛出的异常信息

运行输出:

已经成功触发异常啦!
Traceback (most recent call last):
File "C:/Users/QinTaiW/Desktop/python/test_3.py", line 5, in <module>
raise NameError("这是自定义的异常提示信息") # 附加上参数
NameError: 这是自定义的异常提示信息

在except 内使用一个不带任何参数的 raise 可以再次将上级遇到的异常信息进行抛出(可起到查看异常信息的作用),如果不加以处理,程序仍会终止。

提取异常提示信息

程序在遇到异常后,通常都会提示异常的类型以及引起异常原因的一些相关提示信息。如:NameError: name 'ValueErro' is not defined,其中 NameError 为异常类型, 冒号后面的就是一些提示信息。如果只想提取异常的提示信息,可以使用以下方法。

实例如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

# 定义函数
def temp_convert(var):
    try:
        return int(var)
    except ValueError as Argument:
        print("这是异常提示的信息:" ,Argument)

# 调用函数
temp_convert("xyz");

运行输出:

这是异常提示的信息: invalid literal for int() with base 10: 'xyz'

这里在类型的后面跟上 as 关键字,然后在后面使用 Argument 实例化这个类,程序便可以提取到相关的提示信息,Argument 是一个可变的字符,它并非关键字。

如果直接省略 as Argument 然后在 print() 中直接使用 ValueError 来提取异常提示信息,此时是没有办法提取的,程序会提示这是一个类 class ,最后提取到的是

PS :如果使用的是 Python 2 版本,需要将 as 变成 , 逗号,否则程序将报错。

自定义异常

前面我们所见到的异常信息都是来自内置的异常类,所有的异常信息都是已经定义好的了。我们可以通过创建一个新的类模块来定义自己命名的异常信息,在自定义异常时,应该继承内置的 Exception 类,可以通过"直接继承"或者"间接继承"的方式来实现。

实例如下:

>>> class MyError(Exception):
        def __init__(self, value):
            self.value = value
        def __str__(self):
            return repr(self.value)

>>> try:
        raise MyError(9*9)
except MyError as e:
        print('My exception occurred, value:', e.value)

My exception occurred, value: 81
>>>
>>> raise MyError('Python!')
Traceback (most recent call last):
  File "<pyshell#43>", line 1, in <module>
    raise MyError('Python!')
MyError: 'Python!'

在这个例子中,默认类 Exception 的 init() 将被覆盖。

当创建一个模块有可能抛出多种不同的异常时,一种通常的做法是为这个包建立一个基础异常类,然后基于这个基础类为不同的错误情况创建不同的子类:

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

大多数异常的名字都以"Error"结尾,就跟标准的异常命名一样。

完整学习教程请访问Python3 入门教程——目录索引

最后修改:2022 年 06 月 08 日
如果觉得我的文章对你有用,请随意赞赏