Logo

郎哥编程

处理程序出现的异常

2020-12-24 157

认识Python异常

在前面学习Python语言的过程中,你一定遇到了程序崩溃或因未解决的错误而终止的情况。你会看到Python解释器向你提供的出错信息,包括错误名称、原因和发生错误的行号。这就是程序在执行过程中发生的异常。

我们来看几个Python程序异常的例子。

NameError:尝试访问一个未声明的标识符

>>> width
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'width' is not defined
>>>

因为在程序代码中没有定义width标识符(width是一个变量),Python解释器给出异常信息。

Traceback (most recent call last)是回溯最近发生异常的代码,File "<stdin>", line 1, in <module>意思是发生错误的代码在文件的第1行。

NameError异常表示我们访问了一个没有初始化的变量。Python解释器会在程序的命名空间中查找width,如果在命名空间中没有找到,Python解释器就会引发NameError异常。

ZeroDivisionError:除数为零异常

>>> a=20
>>> b=0
>>> a/b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>

ZeroDivisionError异常表示发生了除数为零的异常。

IndexError:序列对象越界访问异常

>>> student = ["Yohn","David"]
>>> student[0]
'Yohn'
>>> student[2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>>

IndexError异常表示我们在使用索引访问一个序列对象的元素时,已经超出了该序列的索引范围。

为防止异常的发生,开发人员必须要对此类错误进行预防和处理,对异常处理的过程就称为异常处理。在工作和现实生活中,也有很多异常处理的事例。例如,在软件项目开发过程中,团队成员的突然离职、客户对需求的变更、开发进度拖延等情况的发生,都会导致项目开发过程出现异常,并需要项目经理及时处理这些异常情况。

异常不可避免,但可以对异常做出预测和预处理,预测什么情况下会出现异常,以及出现异常后如何处理。

例如,前面谈到的软件项目开发过程出现的异常,可以在项目管理计划书中针对团队成员离职、需求变更、进度拖延出现的异常,制定详尽的应对计划,该应对计划就是异常处理。

对于Python程序而言,内存溢出、访问序列元素超出索引范围、除零操作、输入输出错误等操作都会引发程序异常。因此,我们在编写代码时,需要对上面的情况做出预测,并添加出现异常时的处理语句,以提高程序的稳定性。

在不支持异常处理的程序设计语言中,程序员为了检查可能发生的异常情况,需要使用很多的if—elif语句,这就要求程序员非常清楚地知道是什么导致了异常的产生,以及异常的确切含义。

无异常处理机制的代码如下:

#定义除法函数

def div(a,b):
    if a == None:
        print("a不能为空");
        return -1;
    elif b == None:
        print("b不能为空");
        return -1;
    elif b == 0:
        print("除数不能为0");
        return -1;
    return a/b;
print(div(None,8));
print(div(10,0));
print(div(20,2));

上面的代码定义了div函数,用于除法计算。函数在进行计算之前需要判断传入的参数a和参数b是否符合要求,它们不能为None值、参数b不能为零。计算之前判断参数主要是为了防止除数为零、访问None值异常的发生。

有异常处理机制的语言,没有必要去编写上述的这些if—elif语句。在默认的情况下,异常会输出一个错误消息,并中止程序的执行。为了更好地处理异常情况,程序员通常会在程序中定义异常处理语句来捕获和处理异常情况。Python语言提供了try语句来捕获和处理异常,如何使用try语句在下一节课会详细讲解。

下面我们使用try语句对上面的例子代码提供异常处理的支持,修改后的代码如下:

#定义除法函数
def div(a,b):
  try:
    return a/b;  
  except Exception:
    return "参数错误"
print(div(None,8));
print(div(10,0));
print(div(20,2));

采用try语句不仅可以使代码变得更简洁,而且能为程序调试提供很大的方便,从而达到提高程序稳定性的目的。

异常的处理和检测

在Python程序执行过程中发生的异常可以通过try语句来检测,可以把需要检测的语句放置在try块里面,try块里面的语句发生的异常都会被try语句检测到,并抛出异常给Python解释器,Python解释器会寻找能处理这一异常的代码,并把当前异常交给其处理。这一过程称为捕获异常。如果Python解释器找不到处理该异常的代码,Python解释器会终止该程序的执行。

认识try语句

try语句的语法1如下:

try : suite
(except [expression [as identifier]] : suite)
[else : suite]
[finally : suite]

其中,try是Python关键字,suite是try内的语句块,可以是单条或多条语句。

except是try语句的子句,用于匹配在try语句块发生的异常,可以有多个except子句,expression是一个异常名称,该异常可以被别名,suite是except子句内的语句块。如果except子句没有给出expression,该except子句必须是最后一个子句,该子句将匹配任何异常。

else是可选的子句,如果在try语句块内没有异常发生,else内的语句将会被执行。

finally是可选的子句,finally子句多用于异常处理的善后工作,不管异常是否发生,finally子句内的语句块都会被执行。

try语句的语法2如下:

try : suite
finally : suite

该语法的try语句没有except子句,只有finally子句,try语句内的语句块不管是否发生异常,finally子句的语句块都会被执行,在finally子句内获取不到异常信息。

try语句的使用

案例1:使用try-except-else异常处理语句

案例代码:(见unit6\case6.py)

def div(s):
    try:
        result = s[0] / s[1]
    except ZeroDivisionError as e:
        print(repr(e))
    except IndexError as e:
        print(repr(e))
    else:
        print("无异常发生")
        return result
   
if __name__ == '__main__':
    div([10])
    div([10,0])
div([10,3])

 案例1代码使用了try—except—else语句,用于对异常进行处理。把需要检测发生异常的语句放置在try语句的语句块中,把需要处理异常的语句放置在except子句的语句块中,把需要处理无异常发生的的语句放置在else子句的语句块内。

函数div计算两数相除,并返回两数相除后的结果。函数传入的实参是列表对象。

程序第一次调用div函数传入的列表对象s只有一个元素,在函数内部计算两数相除时,执行s[1]时会抛出IndexError异常,异常抛出后,Python解释器会顺序匹配try语句后面的except子句的异常名称,若匹配成功就执行except子句内的语句块,若匹配失败,异常由Python解释器处理,程序终止执行。

程序第二次调用div函数传入的列表对象s有两个元素,第二个元素的值是0,在函数内部计算两数相除时,执行s[0] / s[1]时会抛出ZeroDivisionError异常。

程序第三次调用div函数传入的列表对象s有两个元素,两个元素的值都大于0,在函数内部计算两数相除时无异常发生,此时else子句内的语句被执行。

try语句块的任何一条语句抛出异常时,程序的控制权移交给except子句,若没有与异常匹配的except子句,程序会终止执行。在一些特殊情况下,这样的处理方式会存在一些问题,例如在一段打开文件并写入数据到文件的代码中,对文件的打开、写入、关闭等操作代码都放置在try语句块中,当执行写入文件的操作抛出异常时,后面关闭文件的语句将不会被执行,从而导致一些系统资源不能被及时释放。在这样的情况下,可以使用finally子句来解决这些问题。

案例2:使用try-except-finally异常处理语句

案例代码:

#将字符串写入文件
str = "课程以浅显易懂的语言,以常见的生活场景为案例,\
            带领大家逐步进入计算机编程世界"
filename = "d://sample.txt"
try:
    fp = open(filename,"w+")
    print("%s 文件打开成功" % filename)
    #写入文件
    size = fp.write(str)
    print("共写入 %d 个字节到  %s" % (size,filename))
except IOError:
    print("%s文件打开失败 " % filename)
finally:
    #关闭文件
    fp.close()
案例2代码用到了内置函数open()及文件对象,关于内置函数open()及文件对象将会在文件处理单元讲解。

在案例2代码中,文件关闭语句被放置在finally子句的语句块中,不管try语句块中代码是否发生异常,打开的文件都将会关闭。

内置异常

课程案例代码用到的IndexError异常、ZeroDivisionError异常、IOError异常都属于Python预定义的内置异常,这些异常和内置函数一样,会被放置在Python的内置命名空间,在整个程序中都可以使用这些异常。

Python预定义的所有异常都是类,except子句处理的异常是异常类的实例(实例也称为对象)。类似于object类,BaseException是所有内置异常的基类,其它所有的异常类都继承于BaseException类。

Python提供了几十个标准异常类,用于处理在不同情况下发生的异常。当不清楚异常需要使用哪个标准异常类时,可以直接使用Exception异常。下表列出了一些常用的异常,其它没列出的异常参见Python文档。

23.png

注释(1):Exception异常

Exception异常是由程序产生异常错误的基类,其它程序产生的异常类继承了Exception类,Exception类继承了BaseException类。

案例代码:

案例代码

def div(s):
    try:
        result = s[0] / s[1]
    except Exception as e:
        print("捕捉到Exception异常:%s" % repr(e))
    else:
        print("无异常发生")
        return result
if __name__ == '__main__':
   div([10,0])

 注释(2):IndexError异常

访问序列对象元素时,若给出的元素索引超出了序列对象的索引范围,会引发IndexError异常。

案例代码:

def div(s):
    try:
        result = s[0] / s[1]
    except IndexError as e:
        print("捕捉到Exception异常:%s" % repr(e))
    else:
        print("无异常发生")
        return result
if __name__ == '__main__':
   div([10])

注释(3):NameError异常

当在程序中使用没有定义的变量、函数时,会引发NameError异常。

案例代码:

def div(s):
    try:
        result = s[0] / num
    except NameError as e:
        print("捕捉到Exception异常:%s" % repr(e))
    else:
        print("无异常发生")
        return result
if __name__ == '__main__':
   div([10])

案例代码div函数内部的num变量没有被定义,执行result = s[0] / num语句时,引发NameError异常。

注释(4):StopIteration异常

由内置函数 next() 和 iterator 的 __next__() 方法所引发,用来表示该迭代器不能产生下一项。

案例代码:

#初始化词组列表
word_list = ['Java','Python','PHP','C++','Basic','Fortran'];
#要求用户输入查询词
query_word = input("请输入查询词:");
#迭代wordList
#获得wordlist迭代器
iterword = iter(word_list);
while True:
    try:
        # 获取迭代器的下一个数据项
        # 若迭代器无法返回数据项,抛出StopIteration异常
        temp_word = next(iterword);
        if temp_word == query_word:
            print("%s匹配成功" % query_word);
            break;
    except StopIteration:
        print("%s匹配失败" % query_word);
        break;

注释(5):SyntaxError异常

当Python解析器遇到语法错误时引发该异常。

案例代码:

>>> t = (1,2,3,4,5,6)
SyntaxError: invalid character in identifier
>>>

案例代码创建元组对象t时,使用了中文小括号,引发SyntaxError异常。

注释(6):TypeError异常

调用函数时,如果传入的实参类型和函数要求的参数类型不一致时,会引发该异常。在执行操作时,参与操作的数据类型与操作要求的数据类型不一致时,也会引发给异常。

案例代码:

>>> num = int(["12"])
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    num = int(["12"])
TypeError: int() argument must be a string, a bytes-like object or a number, not 'list'

int函数要求传入的参数类型为字符串对象、bytes对象、数字类型的对象,案例代码的int函数传入了列表对象,引发了TypeError异常。

注释(7):ValueError异常

当操作或函数接收到具有正确类型但值不适合的参数时引发该异常。

案例代码:

>>> num = int("123t")
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    num = int("123t")
ValueError: invalid literal for int() with base 10: '123t'
>>>

int函数对传入的字符串要求全部是数字型的字符,案例代码传入的字符串包含了非数字型字符,引发了ValueError异常。

注释(8):ZeroDivisionError异常

当除法或取余运算的第二个参数为零时引发该异常。

案例代码:

案例代码:(见unit6\case12.py)

def div(s):
    try:
        result = s[0] / s[1]
    except ZeroDivisionError as e:
        print(repr(e))
    else:
        print("无异常发生")
        return result
if __name__ == '__main__':
    print(div([10,0]))

案例代码调用div函数,传入的列表对象第2个元素是0,引发ZeroDivisionError异常,函数返回None。       

None是Python的内置常量,表示值为空或没有值。

注释(9):IOError异常

文件读取或写入发生错误时引发该异常。

案例代码:

案例代码:(见unit6\case13.py)

#将字符串写入文件
str = "课程以浅显易懂的语言,以常见的生活场景为案例,\
            带领大家逐步进入计算机编程世界"
filename = "d://sample.txt"
try:
    fp = open(filename,"w+")
    print("%s 文件打开成功" % filename)
    #写入文件
    size = fp.write(str)
    print("共写入 %d 个字节到  %s" % (size,filename))
except IOError:
    print("%s文件打写入败 " % filename)
finally:
    #关闭文件
    fp.close()

在案例代码中,当写入文件失败时会引发IOError异常。

注释(10):KeyError异常

当访问不在dict中的键时会引发该异常。

案例代码:

>>> dic = {"one":1,"two":2,"three":3}
>>> dic["one"]
1
>>> dic["five"]
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    dic["five"]
KeyError: 'five'

案例代码试图访问dic不存在的键“five”,引发KeyError异常。

 

代码在线纠错(通义千问 qwen-max)

支持粘贴多个代码文件,提交后由阿里云通义千问自动分析代码漏洞、语法错误、逻辑问题并给出修改建议。
您已解锁 AI 代码纠错功能,可正常使用!

评论区

登录 后发表评论
暂无评论