异常的概念
程序在运行时,如果Python解释器
遇到了一个错误,Python解释器就会停止程序的执行,并且提示一些错误信息,这就是异常。
程序停止执行并提示错误信息的这个动作我们称之为抛出异常。
现在我们来了解一下Python为什么会抛出异常,看一个简单的案例:
In [6]: num = int(input("请输入一个数字 "))
请输入一个数字 a
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
in ()
----> 1 num = int(input("请输入一个数字 "))
ValueError: invalid literal for int() with base 10: 'a'
在这个案例中,我们先输入了一段代码,用于接收用户输入的整数,即num = int(input("请输入一个数字 "))
,但是当我们输入了一个a
后,Python的解释器就报错了,并且解释器提示,输入的这个a
无法用于int()
函数,它只支持10进行的转换,这就是抛出异常。这说明,即使我们输入的是一段在语法上正确的代码,但是它在执行上还有可能报错的,因为我们无法判断用户在输入内容上,用户输入的是一个数字还是字母。
因此当我们面临类似的情况时,我们就需要处理这种特殊情况,我们看一下示意图:
从示意图上我们可以知道,当程序遇到错误时,会抛出异常,这个异常就是我们在设计程序时需要考虑到的情况,这种情况是抛给最初设计程序的程序员的,程序员会针对这种情况输出一定的反馈信息,例如,我们设计程序时,如果有输入性别的选项,有人输入了一个数字,那么就是错误的,程序需要针对这种情况提前设计相应的反馈信息。
捕获异常语法
在程序开发中,如果对某些代码执行不能确定是否正确,可以增加try
来捕获异常,捕获异常最简单的语法格式如下所示:
try:
尝试执行的代码
except:
出现错误的处理
这里解释一下:try
是指下方编写要尝试执行的代码,也就是那些不确定是否能够正常执行的代码。except
指的是,try
下方执行失败后,需要执行哪些代码。
捕获异常的案例
现在还是看前面的那个案例,如下所示:
num = int(input("请输入一个整数: "))
现在我们输入一个4
,结果如下所示:
请输入一个整数: 4
这种情况下,程序运行是正常的,现在我们输入一个a
,程序运行结果如下所示:
请输入一个整数: a
Traceback (most recent call last):
File "D:/netdisk/bioinfo.notes/Python/黑马教程笔记/异常/hm_01_简单的异常捕获.py", line 1, in
num = int(input("请输入一个整数: "))
ValueError: invalid literal for int() with base 10: 'a'
程序运行出错。
上面还是原来的内容,现在我们使用try...except...
语法实现一下类似的功能,如下所示:
try:
num = int(input("请输入一个整数: "))
except:
print("请输入正确的整数: ")
print("-" *50)
现在我们输入一个4
结果如下所示:
请输入一个整数: 4
--------------------------------------------------
现在我们输入一个a
,结果如下所示:
请输入一个整数: a
请输入正确的整数:
--------------------------------------------------
现在我们发现了,except
下面的代码得到了执行。并且两种情况下,最后一行代码,也就是print("-" * 50)
的执行不受影响。
错误类型的捕获
在程序执行时,可能遇到不同类型的异常,并且需要针对不同类型的异常,做出不同的响应,这个时候就需要捕获错误类型了,这种情况下的语法格式如下所示:
try:
# 尝试执行的代码
pass
except 错误类型1:
# 针对错误类型1,对应的代码处理
pass
except (错误类型2, 错误类型3):
# 针对错误类型2和3,对应的代码处理
pass
except Exception as result:
print("未知错误 %s" % result)
当Python
解释器抛出异常时,最后一行错误信息的第一个单词,就是错误类型。
异常类型捕获案例
我们还以前面的让用户输入整数的案例为例说明一下:
这个程序的需求为:①提示用户输入一个整数;②使用8
这个整数除以刚刚用户输入的整数并输出,代码如下所示:
# 提示用户输入一个整数
num = int(input("输入一个整数: "))
# 使用 8 除以用户输入的整数并输入
result = 8/num
print(result)
现在我们输入一个10
,看一下运行结果,如下所示:
输入一个整数: 10
0.8
现在我们再输入一个字母a
,我们看一下结果,结果会出错:
输入一个整数: a
Traceback (most recent call last):
File "D:/netdisk/bioinfo.notes/Python/黑马教程笔记/异常/hm_02_捕获错误类型.py", line 2, in
num = int(input("输入一个整数: "))
ValueError: invalid literal for int() with base 10: 'a'
但是,这个程序还有可能在num
这个地方出错,因为如果用户输入的是一个0
,那么result = 8 / num
中除数就是0了,没有意义,如下所示:
输入一个整数: 0
Traceback (most recent call last):
File "D:/netdisk/bioinfo.notes/Python/黑马教程笔记/异常/hm_02_捕获错误类型.py", line 6, in
result = 8/num
ZeroDivisionError: division by zero
因此针对上面的这些情况,我们都要考虑到这些异常的具体情况,针对每一种具体情况来进行设计。
现在我们还需要了解一点就是,我们如何知道错误类型,前面我们提到,当Python解释器抛出异常时,最后一行错误信息的第一个单词,就是错误类型,现在我们具体的来看一下:
前面的2种错误的第一个单词分别是ValueError
和ZeroDivisionError
,这就是错误类型,现在我们将这个错误类型添加到代码中,如下所示:
try:
# 提示用户输入一个整数
num = int(input("输入一个整数: "))
# 使用 8 除以用户输入的整数并输入
result = 8/num
print(result)
except ZeroDivisionError:
print("除0错误")
运行代码,我们输入一个0
,结果如下所示:
输入一个整数: 0
除0错误
当我们添加了这些代码后,Python解释器就会抛出异常了。
现在我们再来设计针对输入字母这种情况,如下所示:
try:
# 提示用户输入一个整数
num = int(input("输入一个整数: "))
# 使用 8 除以用户输入的整数并输入
result = 8/num
print(result)
except ZeroDivisionError:
print("除0错误")
except ValueError:
print("输入的不是整数")
现在我们运行代码,输入一个字母a
,如下所示:
输入一个整数: a
输入的不是整数
这个案例说明了针对不止一种错误类型的处理方式。
捕获未知错误
在开发中,我们要预判到所有可能出现的错误有一定难度。如果希望程序无论出现任何错误,都不会因为Python解释器抛出异常而被终止,我们可能再增加一个except,如下所示:
except Exception as result:
print("未知错误 %s" % result)
现在我们把前面的案例中的那个除数为0的错误删除,使用这种未知错误来写一下,如下所示:
try:
# 提示用户输入一个整数
num = int(input("输入一个整数: "))
# 使用 8 除以用户输入的整数并输入
result = 8/num
print(result)
except ValueError:
print("输入的不是整数")
except Exception as result:
print("未知错误 %s" % result)
运行结果如下所示:
输入一个整数: 0
未知错误 division by zero
因此,在捕获异常时,最好在最后加上这么这么一句,即except Exception as result:
,其中Exception
是Python是有关错误的一个类。
异常捕获的完整语法
在实际开发中,为了能够处理复杂的异常情况,完整的异常语法如下所示:
try:
# 尝试执行的代码
pass
except 错误类型1:
# 针对错误类型1,对应的代码处理
pass
except 错误类型2:
# 针对错误类型2,对应的代码处理
pass
except (错误类型3,错误类型4):
# 针对错误类型3和4,对应的代码处理
pass
except Exception as result:
# 打印错误信息
print(result)
else:
# 没有异常才会执行的代码
pass
finally:
# 无论是否有异常,都会执行的代码
print("无论是否有异常,都会执行的代码")
其中:
-
else
只有在没有异常时才会执行的代码; -
finally
无论是否有异常,都会执行的代码。
现在我们求救一下前面案例的完整捕获异常,代码如下所示:
try:
# 提示用户输入一个整数
num = int(input("输入一个整数: "))
# 使用 8 除以用户输入的整数并输入
result = 8/num
print(result)
except ValueError:
print("输入的不是整数")
except Exception as result:
print("未知错误 %s" % result)
else:
print("尝试成功")
finally:
print("无论是否出现错误,都会执行的代码")
print("-" * 50)
现在我们输入数字1
,结果如下所示:
输入一个整数: 1
8.0
尝试成功
无论是否出现错误,都会执行的代码
--------------------------------------------------
从结果中我们可以发现,在没有错误出现的情况下,else
与finally
下面的代码都得到了执行。
现在我们输入数字0
,结果如下所示:
输入一个整数: 0
未知错误 division by zero
无论是否出现错误,都会执行的代码
--------------------------------------------------
从结果中我们可以发现,在出现错误出现的情况下,else
下面的代码不执行,直接输出异常情况下的错误信息,并且finally
下面的代码也得到了执行,因为finally
下面的代码无论什么情况下都会执行,并且最后一行的代码print("-" * 50)
也得到了执行,因为抛出异常的完整代码部分就是从try
开始,到finally
结束,之后的代码就都正常执行了。
异常的传递
异常的传递是指,当函数/方法执行出现异常时,会将异常传递给函数/方法的调用一方,此时程序还不会被终止,如果传递到主程序,仍然没有异常处理,这个时候,程序才会被终止。
现在我们来验证一下异常的传递,先来看一下需求:
- 定义函数
demo1()
,提示用户输入一个整数并且返回; - 定义函数
demo2()
,调用demo1()
; - 在主程序中调用
demo2()
。
完整代码如下所示:
def demo1():
return int(input("输入整数: "))
print(demo1())
运行程序,我们输入一个1
,结果如下所示:
输入整数: 1
1
程序运行正常。现在我们输入一个字母a
,如下所示:
输入整数: a
Traceback (most recent call last):
File "D:/netdisk/bioinfo.notes/Python/黑马教程笔记/异常/hm_05_异常的传递.py", line 4, in
print(demo1())
File "D:/netdisk/bioinfo.notes/Python/黑马教程笔记/异常/hm_05_异常的传递.py", line 2, in demo1
return int(input("输入整数: "))
ValueError: invalid literal for int() with base 10: 'a'
程序报错,最下面的错误信息是ValueError: invalid literal for int() with base 10: 'a'
,这是说明,输入的字母无法被int()
函数识别。
我们再往上看,有line 2, in demo1 return int(input("输入整数: "))
字样,也就是说错误出现在了第2行,第2行在转换整数的时候出现了问题。但是,第2行代码出现问题的时候,会把异常交给第4行,也就是解释器出现的这个信息line 4, in
。第4行代码是主程序,也就是print(demo1())
,它是在调用第2行的函数,第2行代码本身是不做异常处理的,主程序中没有做异常处理,因此程序就终止了。
现在我们再改造一下原代码,如下所示:
def demo1():
return int(input("输入整数: "))
def demo2():
return demo1()
print(demo1())
现在我们再输入一个字母a
,如下所示:
输入整数: a
Traceback (most recent call last):
File "D:/netdisk/bioinfo.notes/Python/黑马教程笔记/异常/hm_05_异常的传递.py", line 9, in
print(demo2())
File "D:/netdisk/bioinfo.notes/Python/黑马教程笔记/异常/hm_05_异常的传递.py", line 6, in demo2
return demo1()
File "D:/netdisk/bioinfo.notes/Python/黑马教程笔记/异常/hm_05_异常的传递.py", line 2, in demo1
return int(input("输入整数: "))
ValueError: invalid literal for int() with base 10: 'a'
从错误信息中我们可以知道这些信息:
- 出错的代码还是第2行,提示demo1函数出现的问题;
- 错误信息往上,我们发现了第2行代码出错时,会把错误传递给第6行,因为第6行中的demo2()函数调用了demo1()函数,继续提示demo2出现了问题,但此时我们并没有在demo2内部针对异常进行处理,此时继续向上传递,传递到了第9行;
- 第9行传递到了主程序,也就是
print(deom2())
。
从上面的结果我们可以知道,一旦出现异常,异常会一级一级向上传递,一直到主程序,直到主程序终止运行。
如果我们在实际设计程序中,如果在每一个函数中都设计异常处理,这样代码量会非常大,不现实。因此我们可以利用异常的传递性,可以在主程序中捕获异常即可,如下所示:
def demo1():
return int(input("输入整数: "))
def demo2():
return demo1()
# 利用异常的传递性,在主程序捕获异常
try:
print(demo2())
except Exception as result:
print("未知3错误 %s" % result)
现在运行程序,输入一个字母a
,如下所示:
输入整数: a
未知错误 invalid literal for int() with base 10: 'a'
从结果中我们可以发现,我们只在主程序中添加了异常捕获,就不用在每个函数中对异常进行捕获了。
现在总结一下,利用异常的传递性在开发程序中的好处:可以将精力放在函数的代码逻辑上,不用花太多精力放在异常捕获上,只要函数开发完成,就可以在主程序中添加异常捕获。
抛出异常
在开发中,除了代码执行出错,Python解释会自己抛出异常之外。我们还可以根据应用程序特有的功能来主动抛出异常,现在我们来看一个案例。
例如我们要添加一个用户登录模块,里面有一个函数,这个函数的功能是:提示用户输入密码,如果长度少于8,那么就抛出异常,如下所示:
这里需要注意的是,当前这个函数只负责提示用户输入密码,如果密码长度不正确,需要其他的函数进行额外处理。
抛出异常
Python中提供了一个Exception
异常类。在开发时,如果满足特定功能需求时,希望抛出异常,可以做两件事情:
- 创建一个
Exception
的对象; - 使用
raise
关键字抛出异常对象。
我们来看一下前面案例的需求:
- 定义
input_password
函数,提示用户输入密码; - 如果用户输入长度小于8,抛出异常;
- 如果用户输入长度大于等于8,返回输入的密码。
现在来看一下代码:
def input_password():
# 1. 提示用户输入密码
pwd = input("请输入密码: ")
# 2. 判断密码长度 >= 8, 返回用户输入的密码
if len(pwd) >= 8:
return pwd
# 3. 如果密码 < 8,主动抛出异常
print("主动抛出异常")
# 提示用户输入密码
print(input_password())
现在运行代码,我们输入12345678,结果如下所示:
请输入密码: 123456789
123456789
现在我们输入123,如下所示:
请输入密码: 123
主动抛出异常
None
从结果中我们可以发现,输出了异常,并且还有一个None
,这里为什么会输出None
呢,这是因为在代码中,抛出异常时,并没有任何返回,所以就返回了None
。
现在再更改一下代码,需要创建一个Exception
类对象,如下所示:
def input_password():
# 1. 提示用户输入密码
pwd = input("请输入密码: ")
# 2. 判断密码长度 >= 8, 返回用户输入的密码
if len(pwd) >= 8:
return pwd
# 3. 如果密码 < 8,主动抛出异常
print("主动抛出异常")
# 第一步:创建异常对象
ex = Exception("密码长度不够")
# 第二步:主动抛出异常
raise ex
# 提示用户输入密码
print(input_password())
运行结果,我们输入123,如下所示:
请输入密码: 123
主动抛出异常
Traceback (most recent call last):
File "D:/netdisk/bioinfo.notes/Python/黑马教程笔记/异常/hm_06_抛出异常.py", line 21, in
print(input_password())
File "D:/netdisk/bioinfo.notes/Python/黑马教程笔记/异常/hm_06_抛出异常.py", line 16, in input_password
raise ex
Exception: 密码长度不够
从结果我们可以看出现,程序出错的代码在第16行,也就是raise ex
这一句。根据异常的传递性,继续向上看,异常传递到第22行,也就是print(input_password())
这一句,此时我们使用try来改一下这句,如下所示:
def input_password():
# 1. 提示用户输入密码
pwd = input("请输入密码: ")
# 2. 判断密码长度 >= 8, 返回用户输入的密码
if len(pwd) >= 8:
return pwd
# 3. 如果密码 < 8,主动抛出异常
print("主动抛出异常")
# 第一步:创建异常对象-可以使用错误信息字符串作为参数
ex = Exception("密码长度不够")
# 第二步:主动抛出异常
raise ex
# 提示用户输入密码
try:
print(input_password())
except Exception as result:
print(result)
继续运行,还是输入123,如下所示:
请输入密码: 123
主动抛出异常
密码长度不够
这一次我们就可以发现,运行正常。
在这个案例中我们其实就发现了,我们写了Exception
这个类的对象,程序最终输出的结果就是我们定义的那个对象,即ex = Exception("密码长度不够")
。