1.异常与异常处理
异常就是程序中的错误,正常情况下程序是自上而下逐行执行的,当遇到异常时,就会报错退出执行;
异常处理就是在程序中可能出错的地方进行提前预捕获,并将异常部分的程序进行修正使得程序正常执行。
2.异常的语法
利用try ... except ... 关键字
# 以字符串的upper方法为例
def new_upper(str_data):
new_str = str_data.upper()
return new_str
# 输入正常的字符串,程序执行正常,返回结果正常
new_str = new_upper('test_str')
print(new_str)
'''
TEST_STR
'''
# 输入非字符串类型,程序会报错
new_str2 = new_upper(1)
print(new_str2)
'''
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 15, in <module>
new_str2 = new_upper(1)
File "D:\python_exercise\try_except.py", line 6, in new_upper
new_str = str_data.upper()
AttributeError: 'int' object has no attribute 'upper'
'''
# 接上,此时可以通过try...except...提前捕获异常
def new_upper(str_data):
new_str = 'init_value'
try:
new_str = str_data.upper()
print('try中代码继续执行')
except:
print('传入的字符串参数可能有误,请检查!')
print('整个方法继续执行')
return new_str
# 传入的数据正确时
new_str = new_upper('test_str')
print(new_str)
# try模块中没有异常出现,则不会执行except模块中代码;程序自上而下正常执行
'''
try中代码继续执行
整个方法继续执行
TEST_STR
'''
# 传入的数据出错时,会立即执行except模块中代码,且try中后续代码不再执行,整个方法的后续代码会继续执行
new_str = new_upper(1)
print(new_str)
'''
传入的字符串参数可能有误,请检查!
整个方法继续执行
init_value
'''
3.保存并处理报错信息
上面的例子中,在异常出现时,捕获后做了自定义的处理,没有保留或打印真正的报错信息;
可以在except模块中,捕获并抛出真正的错误信息;
# 有两种处理方式
# 一种是不知道会出现什么错误,用Exception做通用的异常捕获
# 以1不能除0为例, 正常情况下,程序执行异常,会直接报错
1 / 0
'''
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 5, in <module>
1 / 0
ZeroDivisionError: division by zero
'''
# 此时,想拿到Error信息,捕获异常书写方法如下
def test():
try:
1 / 0
except Exception as e: # as e 给异常信息起个别名e(e可以随意起名,一般大家都习惯写e)
print(e)
test()
'''
division by zero
'''
# 另一种是提前知道可能出现的异常类型,可直接按已知异常类型捕获
# 例如例子中已知1/0时的ZeroDivisionError
def test2():
try:
1 / 0
except ZeroDivisionError as e:
print(e)
test2()
'''
division by zero
'''
"""
通用捕获方法,程序执行时需要去异常类型库中找一下报错类型,再去抛出;可能在性能上比指定异常类型差一些,但也可忽略不计;
通用捕获方法,所有异常情况某一种出现都会被抛出,但指定错误类型的方法,当出现其它错误类型时,则不会被抛出,程序依旧报错;
所以两种方式可以按需使用。
"""
# 指定错误类型时,若出现其它错误,程序仍会报错
def test3():
try:
3/4
test_str = 3
test_str.upper()
except ZeroDivisionError as e:
print(e)
test3()
# 此时程序仍会报错
'''
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 51, in <module>
test3()
File "D:\python_exercise\try_except.py", line 47, in test3
test_str.upper()
AttributeError: 'int' object has no attribute 'upper'
'''
上面的例子中,按类型捕获异常时,是可以同时捕获多种类型的;
# 多个异常捕获,依然有两种写法,此时代码中出现某一种错误后就不再进行后续捕获了
# 一种是可以添加多个except模块
def test():
try:
1/0
new_str = 2
new_str.upper()
except ZeroDivisionError as e:
print(e)
except AttributeError as e:
print(e)
print('end')
test()
'''
division by zero
end
'''
# 另一种是写在except元组中
def test2():
try:
1/0
new_str = 2
new_str.upper()
except (AttributeError, ZeroDivisionError) as e:
print(e)
print(type(e)) # 此时要注意下e不是字符串,是错误类型对应的类
print(dir(e)) # 打印e的所有可用方法
print(str(e)) # 有其它需求,可以转化成字符串
print('end')
test2()
'''
division by zero
<class 'ZeroDivisionError'>
['__cause__', '__class__', '__context__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__suppress_context__', '__traceback__', 'args', 'with_traceback']
division by zero
end
'''
4.了解常用的异常类型
Exception 通用异常类型(基类,是所有异常类型的父类)
ZeroDivisonError 不能整除0
AttributeError 对象没有这个属性(或方法)
IOError 输入输出操作失败 (用于文件读写)
IndexError 没有当前的索引 (列表、元组中使用)
KeyError 没有这个键值(key)字典中使用
NameError 没有这个变量 (未初始化对象)
SyntaxError python语法错误
SystemError 解释器的系统错误 (少见)
ValueError 传入的参数错误 (函数传参出错)
TypeError 函数传参类型错误
def test(a):
print(a)
test() # 需要一个必传参数(位置参数),但这里是按默认参数有值传递的,会报错
'''
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 57, in <module>
test()
TypeError: test() missing 1 required positional argument: 'a'
'''
try:
test()
except TypeError as e:
print(e)
'''
test() missing 1 required positional argument: 'a'
'''
5.finally的功能和用法
finally是try...except后的额外操作,无论是否发生异常,都会执行finally代码块;
def upper(str_data):
try:
new_str = str_data.upper()
print('try模块执行')
return new_str
except AttributeError as e:
print(e)
finally:
print('finally模块执行')
return 'finally'
# 调用无异常时
re = upper('test')
print(re)
'''
try模块执行
finally模块执行
finally
'''
# 可以看到无异常抛出时,执行try模块后,会继续执行finally模块
# 且当try中有return返回时,也会再执行finally模块,且当finally中也有return时、最终return结果被finally的覆盖
# 调用出现异常时
re2 = upper(3)
print(re2)
'''
'int' object has no attribute 'upper'
finally模块执行
finally
'''
# 可以看到遇到异常后,直接执行了expect模块
# 执行except模块后,依然执行finally模块
# 有时也会只有try...finally一起使用
# 此时出现异常后,不会被捕获,正常报错,但仍会执行finally模块代码
def test():
try:
1/0
finally:
print('finally执行')
test()
'''
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 119, in <module>
test()
File "D:\python_exercise\try_except.py", line 115, in test
1/0
ZeroDivisionError: division by zero
finally执行
'''
# 此时如果在finally中做return操作
def test2():
try:
print('try1')
1/0
print('try2')
finally:
print('finally执行')
return 'finally re'
re = test2()
print(re)
'''
try1
finally执行
finally re
'''
# 可以看到遇到异常后未抛出程序异常
# 直接正常执行了finally模块code
6.自定义异常与抛出异常
之前我们看到的Exception, NameError, KeyError等错误类型,都是python中自带的抛出异常类型;
在平常业务开发中,我们也可以主动定义抛出一些异常,更好的服务于业务;
'''
使用自定义抛出异常函数 --raise
将信息以报错的形式抛出
'''
def test(number):
if number == 50:
raise ValueError('数字不能是50') # raise后的exception类型可以自选
return number
re = test(100)
print(re) # 100
re2 = test(50)
print(re2)
'''
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 165, in <module>
re2 = test(50)
File "D:\python_exercise\try_except.py", line 159, in test
raise ValueError('数字不能是50') # raise后的exception类型可以自选
ValueError: 数字不能是50
'''
# 自定义的错误类型,也是可以通过try...except捕获到的
try:
test(50)
except Exception as e:
print(e)
'''
数字不能是50
'''
'''
上述例子中valueError正常是表示参数传递参数错误的错误类型,
和编写的函数中数字值错误并不是很贴切,此时可以使用通用的exception来抛出异常
'''
def test(number):
if number == 100:
raise Exception('数值错误,请检查')
return number
re = test(100)
print(re)
'''
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 194, in <module>
re = test(100)
File "D:\python_exercise\try_except.py", line 191, in test
raise Exception('数值错误,请检查')
Exception: 数值错误,请检查
'''
'''
此时还可以自己编写一个异常类型类,更贴切的描述异常
'''
# 需继承Exception基类
class NumberLimitError(Exception):
def __init__(self, message):
self.message = message
def test2(number):
if number == 100:
raise NumberLimitError('数值错误,请检查')
return number
test2(100)
'''
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 219, in <module>
test2(100)
File "D:\python_exercise\try_except.py", line 216, in test2
raise NumberLimitError('数值错误,请检查')
__main__.NumberLimitError: 数值错误,请检查
'''
try:
test2(100)
except NumberLimitError as e:
print(e)
'''
数值错误,请检查
'''
# 顺便提下try...except...else
# 无异常出现时,走else逻辑
try:
test2(50)
except NumberLimitError as e:
print(e)
else:
print('无异常发生')
finally:
print('finally code')
'''
无异常发生
finally code
'''
7.断言
断言是对一个表达式进行判断,在表达式是false时触发异常;
断言关键字assert;assert expresion, message ; (expression是要判断的表达式,结果是bool类型;message是遇到错误要抛出的异常信息)
断言与raise功能类似,相当于对raise的简化写法(使用raise时,会先书写if语句进行判断、再看是否raise抛出异常,assert一行代码就可以搞定这些功能)
assert 1 == 1 # 条件语句为True,正常执行
assert 1 > 2 # 条件语句为False, 会触发报错
"""
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 252, in <module>
assert 1 > 2
AssertionError
"""
# 此时未指定报错信息,只抛出AssertionError异常,未打印信息
import sys
print(sys.platform) # win32
assert ('win32' not in sys.platform), '当前PC型号判断有误'
"""
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 262, in <module>
assert ('win32' not in sys.platform), '当前PC型号判断有误'
AssertionError: 当前PC型号判断有误
"""
8.代码bug调试
bug是程序中的错误,没有提前进行异常捕获,以至于程序报错,导致程序崩溃;
平常开发中代码量较大时,可能不好定位异常的位置,比较耗时,有两种方式可以帮助我们调试程序找到错误;
一种是可以借助print打印信息,辅助定位;
a = 1
print('执行到这里1')
b = 2
print('执行到这里2')
c = 3
print('执行到这里3')
print(a, b, c, d)
'''
执行到这里1
执行到这里2
执行到这里3
Traceback (most recent call last):
File "D:\python_exercise\try_except.py", line 276, in <module>
print(a, b, c, d)
NameError: name 'd' is not defined. Did you mean: 'id'?
'''
# 可以看到'执行到这里3'已成功打印,说明错误出现在'执行到这里3'下一行
# (目前代码量较少,程序已直接打印出了出错的位置,代码调用层级较多时,调试效果会更大些)
另一种是可以借助pycharm的debug功能,利用断点调试程序;
仍然使用相同的代码,在猜测引起报错位置、想要暂停的地方开头点击一下,形成断点;
点击pycharm右上角的debug按钮,开始执行debug模式;
开始debug执行,可以看到每一步已经自动帮我们标注了变量值变化信息(这里的a: 1), 窗口下方会出现debug操作窗;
发现目前没出现错误,想继续执行可以点击底部弹窗的执行按钮,会执行后续所有的代码(如果后续仍有断点,则执行到下一个断点)
可以看到报错位置已经帮我们标注出来了,代码调用层级较多时,可以帮助我们分段调试代码,比较方便;
总结