Python程序设计基础第八章 处理异常_第1页
Python程序设计基础第八章 处理异常_第2页
Python程序设计基础第八章 处理异常_第3页
Python程序设计基础第八章 处理异常_第4页
Python程序设计基础第八章 处理异常_第5页
已阅读5页,还剩26页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

汇报人:WPSPython程序设计基础第八章处理异常目录01异常的基础知识02异常的种类03主动抛出异常04Python生态系统之shutil库目录06拓展实践:给程序做个彩超07本章小结05小试牛刀理解异常。掌握异常处理的相关语句。了解异常的分类。了解匿名函数的使用方法。掌握

enumerate()函数的使用方法。在日常生活中每个人都希望从不生病,然而生病却是不可避免的。在程序编写的过程中人们也

希望不要出现任何异常,但异常也是不可避免的。正如生病了要治病,出现异常就需要处理异常。

本章将介绍什么是异常及处理异常的相关语句。学习目标PART18.1异常的基础知识异常是程序在运行过程中由于各种硬件故障(如网络中断,文件不存在等)、软件设计错误(如

引用不存在的索引)等原因导致的程序错误,这些错误通常会终止程序的运行,使程序崩溃。程序员在编写程序时当然希望避开所有的异常,但由于程序的运行环境、用户的操作行为等因

素都不是程序员可以控制的,所以异常也是不可避免的。例如,代码8.1

接收用户输入的两个数并

输出相除结果,但即便如此简单的一段程序,也有可能出现意外情形而导致异常。代码

8.1

认识异常在用户完全按要求输入时,程序可以正常执行并输出结果。但如果用户输入的不是数字而是字

母如

a,则会出现

ValueError异常,错误信息如下。如果用户输入的是数字,但在输入除数时输入了

0,则会出现

ZeroDivisionError异常,错误信

息如下。如果程序在用户的输入不合预期时就直接崩溃,这样的程序太脆弱,缺乏稳健性和可用性。为了提高程序的稳健性,需要某种机制来对异常进行妥善的处理,使程序不会轻易崩溃。8.1.1异常的概念a=float(input('请输入被除数:'))b=float(input('请输入除数(非

0):'))print("二者的商为",a/b)ValueError:couldnotconvert

stringto

float:

'a'ZeroDivisionError:floatdivisionby

zeroPython针对异常处理的基本策略是使用

try、except等关键字,形成如下的一个结构。

处理异常的基本语法形式try-except结构的工作原理如下:将有可能产生异常的代码书写在

try语句块中,如果这段代码执行时一切顺利,则后面的各

except语句块就像不存在一样。如果

try

语句块的代码执行时出现了某种异常,则根据异常的类型会激发相应的

except分支下的代码,或者说异常被相应的except分支捕获。这里“捕获”的含义是指不要将异常报告给最终用户,从而导致程序崩溃,而是在出现某种异常时,

执行对应的

except下的异常处理代码,这段异常处理代码负责对该类型的异常进行妥善的处理。因此

一个try

语句后可以有多个

except

语句,用来捕获可能产生的多种类型的异常,如代码

8.2所示。代码

8.2

使用

try-except进行异常处理8.1.2异常处理的语法结构try:可能引发异常的代码except

异常类型名称

1:异常处理代码

1except

异常类型名称

2:异常处理代码

2try:a=float(input('请输入被除数:'))b=float(input('请输入除数(非

0):'))print("二者的商为",a/b)exceptZeroDivisionError:print("除数不能为

0")exceptValueError:print("请输入数字")经过

try-except结构的改造后,这段代码在用户输入不合要求的数据时就不会轻易崩溃了。如果

用户输入字母等无法转换为数值型的内容,则程序会提示用户“请输入数字”。如果除数输入了

0,

则程序会提示“除数不能为0”。无论哪一种情形,程序都会给出合理的交互信息而不是粗暴地崩溃,这极大地增强了程序的稳健性。代码

8.2

的运行结果如下所示。处理异常的

try、except语句还可以搭配

as、finally等关键字,形成更完备的异常处理结构。下

面看代码

8.3,这段代码假设在指定位置有一个number_div.txt文件,该文件的每一行都是以冒号分隔的两个数值,程序的目的是计算得出每一行两个数值相除的结果。代码在

try语句块后面预备了

4

except

语句块,并且最后还有一个

finally

语句块。无论

try

语句块的代码是否出现异常,finally

语句块的代码都是要执行的。因此

finally语句块的代码经常用来做一些必需的善后工作,如代码

8.3

中关闭已打开的文件。8.1.2异常处理的语法结构请输入被除数:46请输入除数(非

0):0除数不能为

0请输入被除数:45ada请输入数字代码

8.3使用

try-except-finally结构处理异常8.1.2异常处理的语法结构f_obj=Nonetry:f_obj=open("file/number_div.txt","r")content=f_obj.readlines()forline

in

content:numbers=line.split(":")print(float(numbers[0])/float(numbers[1]))print("计算完毕。")exceptZeroDivisionError:print("除数不能为

0")exceptValueErrorase:err_msg=

str(e)loc=err_msg.find(":")print("文件中出现非数值内容:",err_msg[loc+1:])exceptFileNotFoundErrorase:err_msg=

str(e)loc=err_msg.find(":")print(f"指定的文件

{err_msg[loc+1:]}

不存在")except:print("出现未知错误")finally:if

f_obj

!=None:f_obj.close()print("谢谢使用,再见")仔细观察这段代码中出现的

4个

except语句块,会发现最后一个

except没有指明要捕获的异常

类型,这意味着除前

3个

except语句块所处理的异常类型外,程序出现的所有其他类型异常都由最

后的

except语句块处理。因此这样的

except必须位于众多

except语句块的最后。另外,第

2和第

3个

except语句使用

as关键字为捕获的异常对象命名为

e,这样做的优点是可

以通过名称访问捕获的异常对象,从而进行更多的处理操作。例如,代码

8.3就将捕获的名称为e

的异常对象通过str()函数转换为字符串,这个字符串描述了错误的信息,其中会出现一个冒号,冒

号后的文本往往是引起异常的源头。因此程序找到这部分文本将其输出,以便给用户更多、更有价

值的错误信息。最后的

finally语句块是无论发不发生异常或发生何种异常都要执行的代码段。这里用来关闭打

开的文件,防止在处理文件内部数据的过程中出现异常,程序终止运行来不及关闭文件的情形发生。

但如果是由于文件路径等问题导致

open()函数的执行出现异常,文件并没有成功打开,代码中

f_obj

变量的值会是None,此时就无须关闭文件了。代码

8.3

的运行结果取决于实际情形。如果指定的文件不存在,则运行效果如下。如果文件打开没有问题,但内部数据有非数值内容,则运行结果如下。其中的“ad”就是文件

中引起异常的文本内容。如果文件中的数据符合要求,一切顺利,则运行结果如下。可见无论何种情形,finally的代码都被执行了,“谢谢使用,再见”的字样总会出现在运行结

果中。如果将

try语句块中进行类型转换的

float()函数去掉,则会出现前

3个

except语句块都无法捕获的异常,此时就可以看到第

4个

except语句块的效力了。8.1.2异常处理的语法结构指定的文件'file/number_div.txt'

不存在谢谢使用,再见文件中出现非数值内容:

'ad'谢谢使用,再见7.65计算完毕。谢谢使用,再见PART28.2异常的种类前面的例子涉及了几种Python

常见的异常类型,

如ZeroDivisionError

、ValueError

FileNotFoundError等。它们都是

Python

内置的异常类型,此外,Python

内置的异常种类还有很多,

下面简单介绍几个。(

1

)NameError:当程序尝试访问一个未定义的变量时会引发NameError异常。例如,如果将

代码

8.3

中第

1行的

f_obj定义改为注释,当

open()函数要打开的文件不存在时,最后的

finally语句

块中的

f_obj变量就会触发NameError异常。(2)IndexError:当引用序列中不存在的索引时,会引发

IndexError异常。例如,在一个只有

5

个元素的列表中要访问

5或更大的索引序号时就会触发这种异常。(3)KeyError:当使用映射中不存在的键时,会引发Keyerror异常。例如,访问字典中不存在

的键时,就会带来这种问题。(4)AttributeError:当尝试访问未知的对象属性时,会引发AttributeError

异常。例如,变量a

的类型是整型,在没有将其转换为字符串的情形下却要调用a.find()方法,就会出现“AttributeError:'int'

objecthasnoattribute'find'”的错误信息。8.2.1内置的常见异常种类(5)SyntaxError:当

Python解释器发现语法错误时,会引发

SyntaxError异常。(6)FileNotFoundError:当使用

open()函数试图打开不存在的文件时,会引发

FileNotFoundError

异常。这个异常在代码8.3中已经涉及了。找不到文件的原因可能是提供了错误的文件名,如将

readme.txt错误地拼写为readwe.txt;也可能是路径分隔符与

Python字符串的转义符相冲突造成的。

无论是什么原因导致的,只要将

open()函数放在

try语句块中,再配合捕获

FileNotFoundError类型的

except语句块即可妥善处理这类异常。Python

内置的异常种类很多,这里无法一一讲解,但它们都有共同的特征,是更广泛意义上的

同一类对象。实际上

Python

中所有的异常都是对象,因此

except分支中可以使用

as关键字为捕获

的异常对象命名。每一个异常对象都有其从属的类别,这个类别又有大与小之分,如除数为

0

激发

的异常对象属于

ZeroDivisionError类,也属于更大范围的

ArithmeticError类,当然还属于Exception

类。这就好比一只猫是一个具体的动物,它属于猫科,也可以说它属于哺乳动物类,还可以说它属于动物类。由此可见,Exception类在

Python异常的类别组成中占有重要地位。8.2.1内置的常见异常种类实际上

Python

中“至高无上”的异常类是

BaseException类,它是所有异常的基类。它又包括

4

个子类:SystemExit、KeyboardInterrupt、GeneratorExit、Exception。其中,

最重要的是

Exception类,

它是所有常规异常的基类,之前讲到的常见的异常类型都是

Exception类的子类。程序员如果要自定

义异常,则也需要以

Exception类为父类(类是面向对象编程的基本概念,有关父类、子类的含义可参阅第

10章)。虽然使用

Exception类可以捕获所有常规异常,但是对于所有异常都使用同一段代码处理的话,

会使对异常的处理过于粗糙。通常情况下,应该尽可能使异常处理的粒度细化,以保证每种异常有

合适的处理方式。因此在except分支中使用Exception

类捕获异常应该作为一种后备的异常处理方式。

换句话说,在

except分支中使用

Exception类的效果类似于在

except分支中不指明异常分类。当然,

严格说来二者还是有区别的,毕竟不是所有的异常都属于

Exception类。8.2.2Exception异常类尽管

Python提供了相当丰富的异常类型,但有时为了处理特定的业务逻辑,如某个软件应用所

特有的运行错误,需要根据程序的逻辑在程序中自行定义需要的异常类和异常对象。Python要求程序自行定义的异常类必须继承

Exception

类或其他某个

Exception类的子类。换句

话说,自定义异常类必须以Exception

类为基类。通常的做法是先为自己的程序创建一个派生自

Exception类的自定义异常类,然后从此自定义异常类派生其他异常类。这样不但清晰明了,也方便

管理,如代码

8.4所示。有关类的定义与继承的语法会在第

10章中进行介绍。代码

8.4自定义异常类示意由于大多数

Python

内置异常的名称都以“Error”结尾,所以自定义异常类并对类进行命名时要

遵循同样的风格。8.2.3自定义异常类classMyError(Exception):#MyError类是

Exception类的子类passclassAError(MyError):#AError类是MyError类的子类passclassBError(MyError):#BError类是MyError类的子类passPART38.3主动抛出异常某些情形下,程序运行时也可以主动抛出异常。这种异常一般不是指发生了内存溢出、列表索

引越界等系统级异常,而是程序在执行过程中发生了不符合业务逻辑的情形,主要使用的多是用户自定义异常。主动抛出异常一般可以使用raise语句和

assert语句。代码

8.3从指定的文件number_div.txt

中读取数据,然后进行相应的处理。代码考虑了很多异常

情形,如文件不存在、除数为0、文本无法转换为数值等。这些错误一旦发生,如果不加处理的话

程序就都会崩溃。如果指定文件内部没有任何数据呢?这时在业务逻辑上已经与预期不一致了,但程序运行时并

不会报错、崩溃。如果不进行显式的处理,则很有可能会忽略这种不正常的情形。因此可以将这种

情形定义为一种异常,遇到后主动抛出异常,引起外界注意并进行后期处理。代码8.5

演示了自定义异常类型并使用

raise语句抛出该异常,注意其中粗体显示的代码。8.3.1使用raise语句上报异常代码

8.5

使用

raise语句抛出异常8.3.1写入文本文件classNoDataError(Exception):#自定义的异常类型,当文件中没有数据时触发passf_obj=Nonetry:f_obj=open("file/number_div.txt","r")content=f_obj.readlines()iflen(content)==0:raiseNoDataError("文件没有数据!")forline

in

content:numbers=line.split(":")print(float(numbers[0])/float(numbers[1]))print("计算完毕。")exceptNoDataErrorase:

#捕获

raise语句抛出的异常print(e)exceptZeroDivisionError:print("除数不能为

0")exceptValueErrorase:err_msg=

str(e)loc=err_msg.find(":")print("文件中出现非数值内容:",err_msg[loc+1:])exceptFileNotFoundErrorase:err_msg=

str(e)loc=err_msg.find(":")print(f"指定的文件

{err_msg[loc+1:]}

不存在")except:print("出现未知错误")finally:if

f_obj

!=None:f_obj.close()print("谢谢使用,再见")当指定文件为空文件时,代码

8.5

的运行结果如下。文件没有数据!谢谢使用,再见程序编写完成后都要经过大量的调试,去除各种潜藏的“bug”,这个过程被称为

debug。在调

试过程中,如果程序运行状态已经与某种预期不符,则程序应该及时汇报出现的问题。这可以通过

assert语句实现。assert语句也称为断言语句,它可以指明程序逻辑预期应该满足的条件,当程序实

际运行没有满足该条件时会触发

AssertionError异常。assert语句的用法如下。当程序应满足的条件表达式结果为真时,不会抛出异常。当程序运行不满足条件表达式时,会

引发

AssertionError异常。其中第

2项的描述信息是可选的,一般是一个字符串,用于描述异常信息。8.3.2使用assert语句调试程序assert

应满足的条件表达式,不满足时的描述信息通过设置异常信息,就可以及时发现程序运行与预期不符之处,从而查找原因、改进代码。下面看代码

8.6

的演示。代码

8.6

使用

assert语句调试程序8.3.2使用assert语句调试程序f_obj=open("file/number_div.txt","r")content=f_obj.readlines()try:forline

in

content:numbers=line.split(":")

#中文冒号,与文件使用的英文冒号不符assertlen(numbers)==2,"数据行拆分不正确!"print(float(numbers[0])/float(numbers[1]))print("计算完毕。")exceptAssertionErrorase:print(e)f_obj.close()print("谢谢使用,再见")这段代码在进行数据行以冒号为标志拆分时,误将文件使用的英文冒号书写为中文冒号,因此

会导致拆分失败。这个小错误不仔细看不容易发现。但如果在拆分后添加assert

断言,判断拆分得到的

numbers列表长度是否为

2。如果不是预期的

2,就会激发

AssertionError异常,该异常的描述信息是由

assert语句的第

2个参数指定的。异常被

except语句捕获后,异常处理语句输出“数据行

拆分不正确!”,这样就知道程序运行的故障在哪里,有的放矢地去改进代码。程序最后调试完成后,这些

assert语句都应该从最终版本的程序代码中去除。当然,即使有assert

语句,有其他各种调试功能的帮助,程序员也无法保证代码执行时不出现异常,没有bug。用平和的心态对待异常,尽可能地预备好

except分支的代码,设置好各种预案,异常就没有那么可怕了。PART48.4Python生态系统之shutil库为了完成下面的演示,假设代码文件存在于子文件夹

test1和

test2

中,而

test1文件夹下有一个

read_me.txt文件。代码

8.7演示了各种情形下

copy()函数的工作结果。其中,Windows路径分隔符“\”

每一个都要书写两遍。代码

8.7

使用

copy()函数复制文件代码中的路径使用的是相对路径,点代表当前路径。其实换成以盘符开头的绝对路径道理是一

样的。关键在于目标位置的文件夹要事先存在,否则就会像代码中最后一行的情形,这是因为目标

路径的

read文件夹不存在而无法完成文件的复制。8.4.1使用copy()函数复制文件import

shutil#指明原文件,目标文件名shutil.copy('.\\test1\\read_me.txt','.\\test1\\read.txt')#目标文件名未指定,就使用原文件名shutil.copy('.\\test1\\read_me.txt','.\\test2\\')#test2

文件夹存在,但

test2

文件夹复制下没有me

文件夹#此时me

为目标文件名,原文件被复制到

test2

文件夹下,名为me,没有扩展名shutil.copy('.\\test1\\read_me.txt','.\\test2\\me')#目标路径的

read

文件夹不存在,报错shutil.copy('.\\test1\\read_me.txt','.\\read\\me')

#errorcopy()函数虽然可以将文件复制到指定的目标位置,但原文件的一些元数据(如原文件的访问时

间、修改时间等信息)会丢失。如果不仅需要将文件中的数据复制一份,而且原文件的有关元数据

也要保留,则可以使用

copy2()函数复制文件。下面的代码

8.8使用

copy()与

copy2()函数复制同一个文件,之后使用

os库中的

getmtime()函数

获取原文件与两个复制文件的修改时间(modify

time)。其实每个文件都有创建时间、修改时间、

最近访问时间等

3个常用的时间信息。考虑到文件在复制到新位置后,创建时间就变成了复制动作

发生的时刻,但修改时间保持不变,因此代码中获取的是文件的修改时间。类似的,os库中还有getctime()

getatime()函数用于分别获取创建时间和最后访问时间。8.4.2使用copy()函数复制文件的元数据从代码

8.8

的运行结果中显然可见,copy2()函数复制的文件保留了原文件的修改时间,而

copy()

函数则丢掉了这项信息。代码

8.8使用

copy2()函数复制文件和元数据8.4.2使用copy()函数复制文件的元数据import

shutilimport

os#使用

copy()函数复制文件shutil.copy('.\\test1\\read_me.txt','.\\test1\\read_me_copy.txt')#使用

copy2()函数复制文件shutil.copy2('.\\test1\\read_me.txt','.\\test1\\read_me_copy2.txt')

print("复制完毕。")modify_time_oldfile=os.path.getmtime('.\\test1\\read_me.txt')modify_time_newfile=os.path.getmtime('.\\test1\\read_me_copy.txt')modify_time_newfile2=os.path.getmtime('.\\test1\\read_me_copy2.txt')print("原文件的修改时间:",modify_time_oldfile)print("copy

函数复制的文件的修改时间:",modify_time_newfile)print("copy2

函数复制的文件的修改时间:",modify_time_newfile2)代码

8.8

的运行结果如下,注意

getmtime()函数得到的时间是以时间戳形式显示的。读者实际尝

试时也可以从Windows资源管理器中查看这些文件的修改时间。复制完毕。原文件的修改时间:1676771376.9442756copy

函数复制的文件的修改时间:1676774942.3965209copy2

函数复制的文件的修改时间:1676771376.9442756无论是

copy()函数还是

copy2()函数只能复制单个文件,如果想复制某个文件夹下的所有文件甚

至子文件夹,则可以使用

shutil

库中的

copytree()函数。其使用方法与

copy()函数的使用方法大同小

异,需要注意的是,目标路径的最后一部分不能事先存在,而是由

copytree()函数创建而成的。如果不是复制而是移动文件,则只需将

copy()函数换为

move()函数即可。disk_

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论