7.错误,异常和调试
在Python中,常见的基本错误有两类,及语法错误和异常.对于语法错误,应该在程序编写过程中尽量予以避免,在程序调试中消除.而异常时Python程序在运行过程中引发的错误,如果程序中引发了未进行处理的异常,程序就会由于异常而终止运行,只有为程序添加异常处理,才能使程序更'健壮'.
Python对异常的处理,有它自己的语法形式.通过本章的学习,可以掌握如何在Python中处理异常和进行程序调试,本章主要内容有:
- 语法错误
- 异常的概念
- 用try语句捕获异常
- 常见异常的处理
- 自定义异常
- 使用pdb调试Python程序
7.1 语法错误
语法错误是多有编程语言中都存在的一种常见错误,即程序的写法不符合编程语言的规定.常见的语法错误有:
1.拼写错误
即Python语言中的关键字写错.变量名,函数名拼写错误等.
关键字拼写错误时会提示SyntaxError(语法错误),而变量名,函数名拼写错误会在运行时给出NameError的错误提示,例如:
[实例7.1]演示了一个语法上的错误,这里写错了一个单词,代码如下:
for i in range(3):
printt(i)
[代码说明]实例中代码的第二行中的函数名print被错写成了printt.
[运行效果]会出现NameError错误提示,并同时指出错误所在的行等,如图:
Traceback (most recent call last):
File "D:/PycharmProjects/untitled/YangQing/异常和日志.py", line 4, in <module>
printt(i)
NameError: name 'printt' is not defined
Process finished with exit code 1
2.脚本程序不符合Python的语法规范
例如少了括号,冒号等符号,以及表达式书写错误等.
3.缩进错误
因为Python语法规定,以缩进作为程序的语法之一,这应该是Python语言独特的一面.一般来说Python标准的缩进是以4个空格作为一个缩进.当然,你可以依据自己的习惯,使用Tab也可以.但同一个程序或项目应该统一使用同一种缩进风格.
7.2 异常的处理
异常时Python程序在运行过程中引发的错误.如果程序中引发了未进行处理的异常,脚本就会因为异常而终止运行.只有在程序中捕获这些异常,并进行相关的处理,才能是程序不会中断运行.
7.2.1 异常处理的基本语法
Python中使用try语句来处理异常,和Python中其他语句一样,try语句也要使用缩进结构,try语句也有一个可选的else语句块.一般的try语句基本形式如下:
try:
<语句(块)> #可能产生异常的语句(块)
except<异常名1>: #要处理的异常
<语句(块)> #异常处理语句
except<异常名2>: #要处理的异常
<语句(块)> #异常处理语句
......
else:
<语句(块)> #为触发异常,则执行该语句块
finally:
<语句块> #始终执行该语句,一般为了达到释放资源等目的注意:else语句块在未引发异常情况下得到运行.
在实际应用中可以根据程序的需要而使用部分语句,常见的形式有:
形式一:
try:
<语句块>
except<异常名1>:
<语句块>
[实例7-2]演示了对于没运行时异常的程序,不论是否捕获异常,程序都会正常运行,但是如果程序中发生了运行时异常,被捕获则程序运行不会中断,否则程序运行会中断退出.
#usr/bin/python
#-*-coding:utf-8-*-
# for i in range(3):
# printt(i)
def testTry(index,flag=False):
stulst = ["John","Jenny","Tom"]
if flag: #flag为True时.捕获异常
try:
astu = stulst[index]
except IndexError:
print("IndexError")
return "Try Test Finished!"
else: #flag为False时.不捕获异常
astu = stulst[index]
return "No Try Test Finished!"
print("Right params testing start...")
print(testTry(1,True)) #不越界参数,捕获异常
print(testTry(1,False)) #不越界参数,不捕获异常
print("Error params testing start...")
print(testTry(4,True)) #越界参数,捕获异常
print(testTry(4,False)) #越界参数,不捕获异常
[代码说明]本例定义了一个用于测试捕获异常的函数testTry,flag为True是,函数testTry运行时捕获异常,反之,该函数运行时不捕获异常,当传入的index参数正确时(不越界),测试结果都是正常运行的,当传入index错误(越界)时,如果不捕获异常,则程序运行中断.
[运行效果]如图所示
Traceback (most recent call last):
Right params testing start...
File "D:/PycharmProjects/untitled/YangQing/异常和日志.py", line 21, in <module>
Try Test Finished!
print(testTry(4,False))
No Try Test Finished!
Error params testing start...
File "D:/PycharmProjects/untitled/YangQing/异常和日志.py", line 14, in testTry
IndexError
astu = stulst[index]
Try Test Finished!
IndexError: list index out of range
Process finished with exit code 1
形式二:
try:
<语句块>
except <异常名1>:
<语句块>
finally:
<语句块>
[实例7-3]演示运用finally语句块来确保文件使用后能关闭该文件,代码如下:
def testTryFinally(index):
stulst = ["Jhon","Jenny","Tom"]
af = open("my.txt","wt+")
try:
af.write(stulst[index])
except:
pass
finally:
af.close() #无论是否产生越界异常,都关闭文件
print("File already had been closed!")
print("No IndexError...")
testTryFinally(1) #无越界异常,关闭文件
print("IndexError...")
testTryFinally(4) #有越界异常,关闭文件
[代码说明]本实例定义了一个测试函数testTryFinally,在异常捕获代码中加入了finally代码块,其中的代码块是用来关闭文件,并输出一行信息,无论传入的index参数值是否导致发生运行时异常(越界),总是能正常关闭打开的my.txt文件.}
[运行效果]程序代码运行的结果,如图所示.
No IndexError...
File already had been closed!
IndexError...
File already had been closed!
Process finished with exit code 0
7.2.2 Python主要的内置异常及其处理
- AttributeError 调用不存在的方法引发的异常
- EOFError 遇到文件末尾引发的异常
- ImportError 导入模块出错引发的异常
- IndexError 列表越界引发的异常
- IOError I/O操作引发的异常,如打开文件出错等
- KeyError 使用字典中不存在的关键字引发的异常
- NameError 使用不存在的变量名引发的异常
- TabError 语句块缩进不正确引发的异常
- ValueError 搜索列表中不存在的值引发的异常
- ZeroDivisionError 除数为零引发的异常
except语句主要有以下几种用法:
- except: # 捕获所有异常
- except<异常名>:#捕获指定异常
- except(异常名1,异常名2):#捕获异常名1或者异常名2
- except<异常名>as<数据>:捕获指定异常及其附加的数据
- except(异常名1,异常名2)as<数据>:#捕获异常名1或者异常名2及其异常的附加数据
[实例7-4]演示了程序中若捕获所有异常,则无论运行发生了什么异常,程序都不会中断
def testTryAll(index,i):
stulst = ["John","Jenny","Tom"]
try:
print(len(stulst[index])/i)
except: #捕获所有异常
print("Error!")
print('Try all...Right')
testTryAll(1,2) #正常输出结果
print('Try all...one Error')
testTryAll(1,0) #发生除0异常
print('Try all...two Error')
testTryAll(4,0) #越界异常和除0异常同时发生
[代码说明]代码中定义了函数testTryAll,try语句中捕获了所有的异常,第三次测试中,虽然同是发生了越界和除0异常,但程序不会中断,因为try语句中的except捕获了所有异常.
[运行效果]
Try all...Right
2.5
Try all...one Error
Error!
Try all...two Error
Error!
[实例7-5]演示了程序中捕获了部分异常,当程序运行时引发了不能被捕获的异常时,仍然会中断.
def testTryOne(index,i):
stulst = ["John","Jenny","Tom"]
try:
print(len(stulst[index])/i)
except IndexError:
print("Error!")
print('Try all...Right')
testTryOne(1,2) #正常输出结果
print('Try all...one Error')
testTryOne(4.2) #发生越界异常时被捕获,程序不会中断
print('Try all...two Error')
testTryOne(1,0) #发生除0异常,未捕获,程序中断,有错误提示
[代码说明]到吗中定义了函数testTryOne,其Try语句中只捕获了指明的IndexError异常,因此,当程序引发了IndexError异常时,程序不会中断.而当程序引发了除0异常时,程序会中断运行.
[运行效果]
Traceback (most recent call last):
Try all...Right
2.5
Try all...one Error
File "D:/PycharmProjects/untitled/YangQing/异常和日志.py", line 67, in <module>
testTryOne(4.2) #发生越界异常时被捕获,程序不会中断
TypeError: testTryOne() missing 1 required positional argument: 'i'
Process finished with exit code 1
由此可以看出,捕获所有异常程序,则出现任何错误都不会是程序中断.但是同时捕获所有异常,有事会使程序出现异常时,程序员不知所措,找不到问题所在.
注意:一般情况下,应在程序中指明所要捕获的异常,而不是简单的捕获所有异常.
此外,异常处理的try语句也是可以嵌套的.
7.3 手工抛出异常
所有的异常都是在程序运行中出现了错误而引发的异常,程序员还可以在Python程序中使用raise语句来引发指定的异常,并向异常传递数据.
为了程序的需要,程序员还可以自定义新的异常类型,例如对用户输入的文本的长度的要求,则可以使用raise引发异常,以确保文本输入的长度符合要求.
7.3.1 用raise手工抛出异常
使用raise引发异常十分简单,raise有以下几种使用方式:
raise 异常名
raise 异常名,附加数据
raise 类名
使用raise可以抛出各种预定的异常,即使程序在运行时根本不会引发该异常.
[实例7-6]演示了程序中使用了代码抛出异常,因为没有捕获该异常,所以程序运行会中断,导致后面的代码不能运行.
def testRaise():
for i in range(5):
if i == 2:
raise NameError
print(i)
print('end')
testRaise()
[代码说明]演示了代码中定义了函数testRaise,函数中是一个for循环,当循环变量i为2时,抛出NameError异常,因没有处理该异常而导致程序运行中断,后面的所有输出都得不到执行.
[运行效果]
0
Traceback (most recent call last):
1
File "D:/PycharmProjects/untitled/YangQing/异常和日志.py", line 80, in <module>
testRaise()
File "D:/PycharmProjects/untitled/YangQing/异常和日志.py", line 76, in testRaise
raise NameError
NameError
Process finished with exit code
[实例7-7]演示了程序中使用了代码抛出异常,同时捕获该异常,因此程序运行不会中断
def testRaise2():
for i in range(5):
try:
if i==2:
raise NameError
except NameError:
print('Raise a NameError!')
print(i)
print('end')
testRaise2()
[代码说明]代码中定义了函数testRaise2,函数中是一个for循环,当循环变量i为2时,抛出NameError异常,但是这个异常引发会被捕获处理,程序就不会中断,后面的所有输出都得到执行.
[运行效果]
0
1
Raise a NameError!
2
3
4
end
Process finished with exit code 0
7.3.2 assert语句
assert语句的一般形式如下:
assert <条件测试>,<异常附加数据> #其中异常附加数据是可选的
assert语句是简化的raise语句,它引发异常的前提是其后的条件测试为假.
[实例7-8]演示了程序中使用assert抛出异常,同时捕获了该异常.代码如下:
def testAssert():
for i in range(3):
try:
assert i<2
except AssertionError:
print('Raise a AssertionError!')
print(i)
print('end...')
testAssert()
[代码说明]代码中定义了函数testAssert,函数中是一个for循环,当循环变量是2时,assert后的条件测试为假,抛出AssertionError异常,但是这个异常引发会被捕获处理,程序不会中断,后面的所有输出都得到执行.
[运行效果]程序的运行效果如图:
0
1
Raise a AssertionError!
2
end...
Process finished with exit code 0
assert语句一般用于在程序开发中测试代码的有效性.比如某个变量的值必须在一定范围内,而运行时得到的值不符合要求,则引发该异常,对于开发者予以提示.所以一般在程序开发中,不去捕获这个异常,而是让它中断程序.原因是程序中已经出现了问题,不应继续运行.
assert语句并不是总运行的,只有Python中内置的一个特殊变量__debug__为True时才运行.要关闭程序中的assert语句就使用python -O(短画线,后接大写字母O)来运行.如实例7-8用这个方法运行的结果如图7.10所示,很明显其中的assert语句被关闭,没有运行并引发异常:
D:\PycharmProjects\untitled\YangQing>python -O 异常和日志.py
0
1
2
end...
7.3.3 自定义异常类
在python中定义异常类不用从基础完全定义,只要通过继承Exception类来创建自己的异常类.异常类的定义和其他类没有区别,最简单的自定义异常类甚至可以只继承Exception类,类体为pass 如:
class MyError(Exception): #继承Exception类
pass
如果需要异常类带有一定的提示信息,也可以重载__init__和__str__这两个方法.
[实例7-9]演示了程序自定义了一个异常类,并用代码引发异常,代码如下:
class RangeError(Exception):
def __init__(self,value):
self.value = value
def __str__(self):
return self.value
raise RangeError('Range Error!')
[代码说明]代码中定义了一个继承了Exception类的异常类,并重载了__init__和__str__两个方法,之后,直接用raise抛出这个自定义的异常.
[运行效果]如图.
Traceback (most recent call last):
File "D:/PycharmProjects/untitled/YangQing/异常和日志.py", line 122, in <module>
raise RangeError('Range Error!')
__main__.RangeError: Range Error!
Process finished with exit code 1
7.4 用pdb调试程序
python解释器可以发现程序中的语法错误,在试运行时会中断执行并给出提示.但是程序中逻辑上的错误,或其他非语法错误不会被发现.虽然程序能够正常运行,但是运行后得不到预想的结果,这就要对程序进行调试.
调试程序可以用python自带的pdb模块,其功能有设置断点,单步执行,查看变量等.他可以用命令行参数的形式启动,也可以通过导入模块使用.常用的pdb模块的函数可以分为以下几类:
7.4.1调试语句块函数
pdb模块中的调试语句块的函数及参数原型为:
run(statement[,globlas[,locals])
- statement 要调试的语句块,以字符串的形式表示;
- globals 可选参数,设置statement运行的全局环境变量;
- locals 可选参数,设置statement运行时的局部环境变量;
[实例7-10]交互模式下pdb模块调试,代码如下:
import pdb
pdb.run("""
for i in range(3):
print(i)
""")
[代码说明]代码中首先导入了pdb模块,调用pdb模块的run函数来调试一段简单的Python代码(字符串形式).
[运行效果]如下图所示,是交互模式下调试,其中"(pdb)"是pdb调试的提示符.在提示符下使用h(elp)命令可以查看所有的调试命令.n命令表示执行语句的下一句;continue命令表示继续执行以后的程序段.此外,print(r)命令可用于输出变量的当前值,其它命令参考表7.2:
<string>(3)<module>()则表示即将执行代码语句行数和所在模块为匿名模块.
> <string>(2)<module>()
(Pdb) h
Documented commands (type help <topic>):
========================================
EOF c d h list q rv undisplay
a cl debug help ll quit s unt
alias clear disable ignore longlist r source until
args commands display interact n restart step up
b condition down j next return tbreak w
break cont enable jump p retval u whatis
bt continue exit l pp run unalias where
Miscellaneous help topics:
==========================
exec pdb
(Pdb) exec
<built-in function exec>
(Pdb) n
> <string>(3)<module>()
(Pdb) continue
0
1
2
Process finished with exit code 0
完整命令 简写命令 描述
args a 打印当前的函数的参数
clear cl 清除断点
break b 设置断点
condition 无 设置条件断点
continue c或者count 继续运行,只要遇到断点或者程序结束
disable 无 禁用断点
enable 无 启用断点
help h 查看pdb帮助
ignore 无 忽略断点
jump j 跳转到指定行数运行
list l 列出程序清单
next n 执行下条语句,遇到函数不进入其内部
p p 退出pdb
return r 一直运行到函数返回
tbreak 无 设置临时断点,断点只中断一次
step s 执行下一条语句,遇到函数进入其内部
where w 查看所在的位置
! 无 在pdb中执行语句
7.4.2 调试函数
pdb模块中的调试函数应当调用runcall函数,其参数原型为:
runcall(function[,argument,...])
- function 函数名;
- argument 函数的参数;