笔者:风起怨江南 出处:https://blog.csdn.net/JackMengJin 笔者原创,文章转载需注明,如果喜欢请点赞+关注,感谢支持!
导读:想象一种场景,自己公司的项目今天上线,由于之前一个没有测试到的bug(比如数组越界)直接导致了系统崩溃,停止运行,整个项目组的成员会不会很抓狂?极大可能还会给公司和个人带来巨大的损失。虽然谁也不能保证软件没有bug,那类似这种未知的bug应该如何防范呢?
目录
Python的异常处理:捕获异常和抛出异常
1.什么是异常?
2.异常类型
3.捕获异常
4. try的工作原理
5.捕获异常进阶版
6.raise抛出异常
7.try-finally 语句
8.什么时候用异常处理?
什么是异常?bug就是异常。
当程序出现了意想不到的情况,比如下标索引超出序列边界,传入一个不被期望的值等等情况时,程序就会出现异常,系统运行到异常时就是崩溃,停止运行。
异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。
一般情况下,在Python无法正常处理程序时就会发生一个异常。
异常是Python对象,表示一个错误。
list_demo = [1,2,3,4,5,6]
print(list_demo[6])
Traceback (most recent call last):
File "E:/博客/demo.py", line 5, in
print(list_demo[6])
IndexError: list index out of range
程序出现异常后,程序后面的代码将不会被执行。
list_demo = [1,2,3,4,5,6]
print(list_demo[6])
print('我是K歌之王,你是苦瓜')
Traceback (most recent call last):
File "E:/博客/demo.py", line 5, in
print(list_demo[6])
IndexError: list index out of range
有人会觉得,程序出现异常报错不应该是好事么?没错,在代码调试阶段程序早点出现异常当然是好事,这样可以提早完成对代码bug的修改。但现实中异常(bug)一般隐藏在相对于复杂的场景中,不容易被发现,测试也很难完全覆盖到:
def operation(num1,num2):
"""运算函数"""
num3 = (num1 / num2) ** 3 // 2
return num3
比如一个需要传参的复杂函数来说,参数的值在测试时并不能完全覆盖到,当函数中传入的是常规的实际参数进行测试时,很难将异常(bug)发现:
print(operation(2,1))
print(operation(-7,5))
print(operation(200,33))
42.0
-4.0
100113.0
比如涉及到除法运算时,却没有考虑到被除数为0的情况,那么只会导致系统报错,停止运行:
print(operation(2,1))
print(operation(-7,0))
print(operation(200,33))
42.0
Traceback (most recent call last):
File "E:/博客/demo.py", line 10, in
print(operation(-7,0))
File "E:/博客/demo.py", line 6, in operation
num3 = (num1 / num2) ** 7 // 3
ZeroDivisionError: division by zero
可能有人会说除法的被除数不能为0这个谁都知道,这里加个判断不就行了么?但又该怎么去保证其他代码呢?
就像导读里说的那样,项目上线了,如果出现了一个之前测试中疏忽的异常,那应该怎么办?
下面就要进入今天的主题,Python的异常处理。
既然出现异常,就要对异常进行处理,要想办法把异常的问题解决掉。
python提供了两个非常重要的功能来处理python程序在运行中出现的异常和错误,一个是异常处理,另一个是断言(Assertions)。断言的内容在之后学习中再单独讲解。
Python中有多少个异常类型?如下表所示:
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
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 | 用户代码生成的警告 |
最为常见的是以下几种异常类型:
需要注意的是,语法错误是不能被异常捕获!!!
捕获异常的定义:
如果出现了异常,我们会让他按照事先规定的规则,去执行对应的操作。
try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。
如果你不想在异常发生时结束你的程序,只需在try里捕获它。
Python中捕获异常使用的是try/except语句。try/except的基本用法:
try:
写有可能出现错误的代码
except:
出现错误,可以记录错误日志
举例:
def operation(num1,num2):
"""运算函数"""
num3 = (num1 / num2) ** 7 // 3
return num3
try:
print(operation(2,1))
print(operation(-7,0))
print(operation(200,33))
except:
print('出错了!')
运行:
42.0
出错了!
运行后没有报错。
在使用try...except异常捕获后,当运行到 print(operation(-7,0)) 时,本来会报错终止程序,但程序可以正常运行,运行后也不再报错。同时执行了 except 下面的代码 print('出错了!') 。
也就是当try里面的语句执行时发生了异常,try里面的后面的代码就不在执行,而是直接跳到expect里去执行里面的代码。
当使用try语句时,python就在程序的上下文中作标记。
try子句会先执行,接下来会的处理会依赖于执行时是否出现异常,当异常出现时,这也是刚开始在上下文中作标记的作用。
工作原理:
也就是当try语句执行时发生了异常,就会直接跳到该try语句的except子句中,去执行except里的代码。当执行完except里的代码后,该try语句的异常处理就算处理完成,try异常后面的代码将不会再执行,整个程序会跳出该try语句,执行程序后面的代码。
总结:
掌握了基本的try/except语句用法,这里引出捕获异常的进阶用法。
表示:
try:
写有可能出现错误的代码
except Exception as err :
print('出现错误类型:{}'.format(err))
举例:
def operation(num1,num2):
"""运算函数"""
num3 = (num1 / num2) ** 7 // 3
return num3
try:
print(operation(2,1))
print(operation(-7,0))
print(operation(200,33))
except Exception as err:
print('出错了,错误类型为:{}'.format(err))
42.0
出错了,错误类型为:division by zero
Exception是异常的统称,except Exception as err 其实就是将捕获到的错误,赋值给err变量。这里的err可以取任意变量名来表示,这样就可以通过打印或者日志记录来将异常里的错误原因进行记录。
如果已经得知会可能出现的报错原因时,我们可以将异常类型直接放在except语句中,而常用的异常类型已经在上面讲过了,直接拿来用即可。
try:
print(operation(-7,0))
except ZeroDivisionError as err:
print('出错了,错误类型为:{}'.format(err))
所有的异常类型都可以放在except中,这样可以对错误类型就更加准确的定位,并对每一个异常都区别对待:
try:
print(operation(2,1))
print(operation(-7,0))
print(operation(200,33))
except ValueError as err:
print('出错了,错误类型为:{}'.format(err))
except IndexError as err:
print('出错了,错误类型为:{}'.format(err))
except NameError as err:
print('出错了,错误类型为:{}'.format(err))
except ZeroDivisionError as err:
print('出错了,错误类型为:{}'.format(err))
42.0
出错了,错误类型为:division by zero
但这种方式如果没把实际的异常类型写入,还是会出现报错:
try:
print(operation(2,1))
print(operation(-7,0))
print(operation(200,33))
except ValueError as err:
print('出错了,错误类型为:{}'.format(err))
except IndexError as err:
print('出错了,错误类型为:{}'.format(err))
except NameError as err:
print('出错了,错误类型为:{}'.format(err))
# except ZeroDivisionError as err:
# print('出错了,错误类型为:{}'.format(err))
Traceback (most recent call last):
File "E:/博客/demo.py", line 11, in
print(operation(-7,0))
File "E:/博客/demo.py", line 6, in operation
num3 = (num1 / num2) ** 7 // 3
ZeroDivisionError: division by zero
所以在expect子语句中使用具体的异常类型时,一定要提前运行得知会出现的异常类型,如果不知道具体的异常类型,还是用Exception效果更好,至少不会报错。
如果说expect只是捕获异常,不报错,那么raise就是抛出异常,会报错。
也就是给代码里人为手动的抛出异常,让程序终止运行。
用法:raise 异常类型(‘内容’)
list_demo = [1,2,3,4,5,'Jack']
if 'Jack' in list_demo:
raise ValueError('Jack在列表中!')
print('外面下了很大的雨')
Traceback (most recent call last):
File "E:/博客/demo.py", line 6, in
raise ValueError('Jack在列表中!')
ValueError: Jack在列表中!
一个异常可以是一个字符串,类或对象。 Python的内核提供的异常,大多数都是实例化的类,这是一个类的实例的参数。
需要注意,触发异常后,后面的代码就不会再执行。触发异常就跟正常运行遇到异常时是一样的。
try-finally 语句无论是否发生异常都将执行最后的代码。也就是不管有没有报错,finally里的代码都会执行。
def operation(num1,num2):
"""运算函数"""
num3 = (num1 / num2) ** 7 // 3
return num3
try:
print(operation(2,1))
print(operation(-7,0))
print(operation(200,33))
except Exception as err:
print('出错了,错误类型为:{}'.format(err))
finally:
print('运行结束')
42.0
出错了,错误类型为:division by zero
运行结束
即使报错,也会执行finally子语句中的代码:
def operation(num1,num2):
"""运算函数"""
num3 = (num1 / num2) ** 7 // 3
return num3
try:
print(operation(2,1))
print(operation(-7,0))
print(operation(200,33))
except ValueError as err:
print('出错了,错误类型为:{}'.format(err))
finally:
print('运行结束')
Traceback (most recent call last):
File "E:/博客/demo.py", line 11, in
print(operation(-7,0))
File "E:/博客/demo.py", line 6, in operation
num3 = (num1 / num2) ** 7 // 3
ZeroDivisionError: division by zero
42.0
运行结束
由于无论如何都会执行finally里的代码这一特点,所以finally适合在一些特殊场景中使用,比如进行一些关闭数据库,对文件的关闭等场景处理。
既然异常处理这么好用,那么异常处理什么时候用比较好?
其实对于一个好的程序员和一般的程序员来说,最大的区别就是:
好程序员知道什么时候出现异常,而一般的程序员不知道什么时候出现异常。
在实际项目里,当我们觉得这个地方可能出现问题时,就可以使用异常处理。try写进去的代码,一定是可能会出现异常的代码,不知道会不会出现问题,但凡觉得有可能出现问题的地方,就用try语句准没错。
比如Python自动化测试中,每次执行成百条的测试用例,会用断言预期结果和实际结果中使用异常处理。这样可以保证在执行用例的时候出现异常也不会终止程序,从而继续执行剩下的测试用例。而捕获到的异常可以记录到测试日志里,在执行完所有测试用例后,可以在日志里查看到具体的异常内容。
异常处理的具体使用会在后续学习博客中经常出现,希望大家可以持续关注。
以上便是《Python学习18:Python的异常处理》的所有内容,原创不易,如果喜欢请点赞和关注,谢谢大家的支持!
想获得免费的学习资料请添加微信公众号——风起怨江南。