Python有两种错误很容易辨认:语法错误和异常。
1 什么是语法错误
Python 的语法错误或者称之为解析错,是初学者经常碰到的,如下实例
if i>4 print("if语句输出")
运行
File "test.py", line 1 if i>4 ^ SyntaxError: invalid syntax
这段代码中,if 后面的语句没有冒号(:),被语法分析器指出了出错行,并在最先找到的错误处标记一个小小的箭头。
语法分析器指出了出错的一行,并且在最先找到的错误的位置标记了一个小小的箭头。
这个例子中,函数 print() 被检查到有错误,是它前面缺少了一个冒号(:)。
2 什么是异常
即使语法是正确的,但运行过程中也有可能发生错误。运行期检测到的错误被称为异常。
大多数的异常都不会被程序处理,都以错误信息的形式呈现出来。
不同的异常类型以不同的信息内容打印出来。
错误信息的前面部分显示了异常发生的上下文,并以调用栈的形式显示具体信息。
当异常发生时,程序不会再向下执行,而转去调用此函数地方待处理此错误并恢复为正常状态。
注意:虽然大多数错误会导致异常,但一个异常不一定代表错误,有时候它们只是一个警告,有时候它们可能是一个终止信号,比如退出循环等。
有的错误是程序编写有问题造成的,比如本来应该输出整数结果输出了字符串,这种错误我们通常称之为bug,bug是必须修复的。
有的错误是用户输入造成的,比如让用户输入email地址,结果得到一个空字符串,这种错误可以通过检查用户输入来做相应的处理。
还有一类错误是完全无法在程序运行过程中预测的,比如写入文件的时候,磁盘满了,写不进去了,或者从网络抓取数据,网络突然断掉了。这类错误也称为异常,在程序中通常是必须处理的,否则,程序会因为各种问题终止并退出。
Python内置了一套异常处理机制,来帮助我们进行错误处理。
此外,我们也需要跟踪程序的执行,查看变量的值是否正确,这个过程称为调试。Python的pdb可以让我们以单步方式执行代码。
最后,编写测试也很重要。有了良好的测试,就可以在程序修改后反复运行,确保程序输出符合我们编写的测试
3 异常的作用
通知上层调用有错误产生需要处理
用异常作为信号通知调用者
4 捕获异常
为了方便处理异常,高级语言通常都内置一套 try...except...finally... 的错误处理机制。
Python语言的 try 语句有两种语法结构
try-except 语句 try-finally 语句
4.1 try-except 语句
try: 可能触发异常的语句: except 错误类型 1 [as 变量1]: 异常处理语句1 except 错误类型 2 [as 变量2]: 异常处理语句2 except (错误类型3, 错误类型4,...) [as 变量4]: 异常处理语句3 .... except: 其它异常处理语句 else: 末发生异常语句 finally: 最终语句
示例:
def div(n): a = 10/n print("10/n = ",a) try: get_num = int(input("请输入值:")) div(get_num) except ZeroDivisionError as a: print("零不能作为被除数") print('except',a) except ValueError as a: print("非数字内容不能进行类型转换") print('except',a) else: print("程序没有出现异常,一切OK!") finally: print('finally...') print('程序结束')
运行示例1
请输入值:2 10/n = 5.0 程序没有出现异常,一切OK! finally... 程序结束
当程序正式运行时,代码中没有出现任何错误,则 else 中的内容会被执行, finally 中的语句会被执行,当然了,这两段代码是可以去掉的。
运行示例2
请输入值:0 零不能作为被除数 except division by zero finally... 程序结束
当执行函数 div() 中的 a = 10/n 过程中发生错误时,会抛出异常,此时不向下执行,也即函数中语句 print("10/n = ",a) 不会输出出来。
由于 except 捕获到异常 ZeroDivisionError ,则执行该语句下面的内容,对异常进行处理,执行完毕后在执行 finally 语句。
这里as是绑定错误对象的变量,可以省略。
运行示例3
请输入值:a 非数字内容不能进行类型转换 except invalid literal for int() with base 10: 'a' finally... 程序结束
同样当int()转换出现异常时,则不会再调用函数 div() ,而是抛出异常,而 except 捕捉到 ValueError 异常,接下来的处理过程与示例2类似。
4.1.1 try-except语法说明:
1)as 子句用于绑定错误对象的变量,可以省略
2)except 子句可以有一个或多个,但至少要有一个
3)else子句最多只能有一个,也可以省略不写
4)finally子句最多只能有一个,也可以省略不写
4.1.2 try-except执行流程
1) 当try内的语句无错误时,执行正常流程,然后执行else子句和finally子句
2) 当try内的语句有异常发生时,所有正常流程终止,转去寻找包含此语句的try中的except部分。如果类型匹配则转为正常流程,否则异常状态会向上层传递
3) 无论是否有异常发生,finally子句永远会执行,else只有在正常的执行其后的句子
4.1.3 异常的抛出机制
1)如果在运行时发生异常,解释器会查找相应的处理语句(称为handler).
2)要是在当前函数里没有找到的话,它会将异常传递给上层的调用函数,看看那里能不能处理。
3)如果在最外层(全局“main”)还是没有找到的话,解释器就会退出,同时打印出traceback以便让用户找到错误产生的原因。
4.2 try-finally 语句
4.2.1 语法
try: 可能触发异常的语句 finally: 最终语句
其实,该语句也可以理解成 try…except…finally 语句中的特殊情况,将中间 except 语句省去而成的。
这里的 finally 句子不能省略
4.2.2 作用
通常用try...finally语句来做触发异常时必须要处理的事情,备注:该语句不会改变程序的(正常/异常)状态
4.2.3 示例
def fry_egg(): print("打开天燃气....") try: eggs = int(input("请输入鸡蛋个数: ")) print("正在煎 ", eggs, "个鸡蛋") print("完成煎蛋") finally: print("关闭天燃气!!!!!") fry_egg() print("程序结束!")
运行(过程中输入1)
打开天燃气.... 请输入鸡蛋个数: 1 正在煎 1 个鸡蛋 完成煎蛋 关闭天燃气!!!!! 程序结束!
或者(运行过程中输入a)
打开天燃气.... 请输入鸡蛋个数: a 关闭天燃气!!!!! Traceback (most recent call last): File "test.py", line 10, in <module> fry_egg() File "test.py", line 4, in fry_egg eggs = int(input("请输入鸡蛋个数: ")) ValueError: invalid literal for int() with base 10: 'a'
通过这个可以知:无论怎么操作,finally 语句均可以执行。
这个语句也可以优化
def fry_egg(): print("打开天燃气....") try: eggs = int(input("请输入鸡蛋个数: ")) print("正在煎 ", eggs, "个鸡蛋") print("完成煎蛋") finally: print("关闭天燃气!!!!!") try: fry_egg() except: print("程序由异常转为正常状态了!!!!") print("程序结束!")
运行(过程中输入1)
打开天燃气.... 请输入鸡蛋个数: 1 正在煎 1 个鸡蛋 完成煎蛋 关闭天燃气!!!!! 程序结束!
或者(过程中输入a)
打开天燃气....
请输入鸡蛋个数: a
关闭天燃气!!!!!
程序由异常转为正常状态了!!!!
程序结束!
4.3 finally子句
为了更好地说明finally语句的作用,这里单独说明。
finally子句在try中无论捕获到异常均会执行该子句代码。这在类似文件关闭、释放锁、数据库连接返还给连接池等操作时非常重要。
如果我们把f.close语句放到finally语句中,无论是否有异常,都会正常关闭这个文件。
5 raise抛出异常
有时候在编写程序时会利用raise主动抛出异常。
生成一个错误, 让程序进入异常状态
语法:
raise 异常类型 或 raise 异常对象
示例:
>>> raise NameError #抛出错误类型 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError >>> raise NameError("这是一个名字错误") #带有异常信息参数的错误类型 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: 这是一个名字错误 >>> i = 10 >>> raise NameError("这是一个名%d字错误"%i) #异常对象信息进行初始化的参数 Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: 这是一个名10字错误 >>> j = "一" >>> raise NameError("这是%s个名字错误"%j) Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: 这是一个名字错误
注:
执行raise语句时,Python会创建指定的异常类的一个对象。raise语句还可指定对异常对象进行初始化的参数。
尽管错误是通过raiser人为定义的,但是定义的异常类型必须是Python提供的。
6 assert 语句 (断言语句)
6.1 语法
assert expression[,reason]
expression 真值表达式
reason 错误数据(通常是字符串)
执行该语句时,先判断expression表达式是否为真,如果为真,则什么都不做,如果表达式不为真,则抛出异常——AssertionError 类型!
6.2 作用
当真值表达式为False 时,用错误数据创建一个 AssertionError 类型的错误,并进入异常状态
等同于:
if not 真值表达式: raise AssertionError(错误数据)
6.3 示例
示例1 抛出 AssertionError 异常
>>> assert 1 == 1 >>> assert 1 == "1" Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError
示例 2
def get_score(): s = int(input("请输入学生成绩:")) assert 0 <= s <= 100, "成绩超出范围!" # 等同于如下语句 # if not (0 <= s <= 100): # raise AssertionError("成绩超出范围!") return s try: score = get_score() print("您输入的成绩是: ", score) except AssertionError: print("报告,有人作弊!!") score = 0
运行(过程中输入60)
请输入学生成绩:60
您输入的成绩是: 60
运行(过程中输入200)
请输入学生成绩:200
报告,有人作弊!!
7 Python常用的错误类型
ZeroDivisionError 除(或取模)零
StopIteration 迭代器没有更多的值
ImportError 导入模块对象失败
GeneratorExit 生成器发生异常通知退出
IndexError 序列中没有此索引
IndentationError 缩进错误
ValueError 传入无效的参数
NameError 末声明/初始化对象
AttributeError 对象没有这个属性
AssertionError 断言语句失败
IOError 输入/输出操作失败
打开 >>> help(__builtins__) 可以查看所有异常类型
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
SystemExit | Python 解释器请求退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
LookupError | 无效数据查询的基类 |
IndexError | 序列中没有没有此索引(index) |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
NameError | 未声明/初始化对象 (没有属性) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError | Python 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning | 关于特性将会被废弃的警告 |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |
补充
1 异常多用在通知用。
2 当发生异常时,系统会层层拦截,如果在程序中没有拦截成功,则会通过内核来终止程序,抛出异常。
3 在程序调试过程中raise函数抛出异常可以和pdb调试程序联合使用