文章目录
背景
当编写Python代码时,我们对不可预料的程序问题提前进行预防,使得输入(输出)数据潜在的错误和异常能够被及时找出并处理,保证主程序不受影响,而这种预防动作就是防御式编程(Defensive Programming),它是在实际工程中常用的一种手段。
下面将介绍几种Python中常见的防御式编程的示例,涵盖了raise
、try-except
和assert
等概念。这些预防机制一般用在:输入验证、异常处理、边界条件检查等场景。
1. 使用raise抛出异常
如下例子,在除法函数 divide
中,当判断到除数 b==0
时,触发 raise
抛出ZeroDivisionError
异常,并提示说“除数不能为零”,当程序运行至抛出错误后,终止运行。
def divide(a, b):
if b == 0:
raise ZeroDivisionError("除数不能为零")
return a / b
ZeroDivisionError: 除数不能为零
2. 使用try捕获和处理异常
有时候我们希望异常发生时,代码还能继续运行,可能是输出错误信息,也可能是往另外的代码分支运行,总之不希望代码因为错误而直接停止时,可以考虑用 try - except
。
(1)捕获和处理指定异常类型
以下示例演示了用 try
运行一段会报错的代码,被捕捉到 ZeroDivisionError
错误后,代码继续运行,输出后续的内容。
def divide(a, b):
if b == 0:
raise ZeroDivisionError("除数不能为零")
return a / b
try:
value_ = divide(2, 0)
except ZeroDivisionError as e:
print(f"Exception caught: {e}")
value_ = 0
print("Continuing with the program...")
print("Result: ", value_)
打印的结果为:
Exception caught: 除数不能为零
Continuing with the program...
Result: 0
(2)捕获和处理多个异常类型
进一步地,当我们知道某段代码可能会报错的几种类型,但不确定具体是哪一类,此时可以多写几层 except
来捕获多种异常类型。
如下例子:
try:
file = open("example.txt", "r")
content = file.read()
file.close()
except FileNotFoundError:
print("文件不存在")
except PermissionError:
print("无权限访问文件")
该例子首先尝试打开和读取文件内容,不论哪一句代码发生错误,都会抛出异常,此时被 except
进行捕获,当判断是第一类错误FileNotFoundError
时,则打印“文件不存在”,当判断为第二类错误PermissionError
,则返回“无权限访问文件”。
(3)使用finally子句清理资源
在try-except
结构中,常常会看到最后还有finally
,不论前面的 try
是否发生异常,finally
下的代码均会被执行。其实也可以像前面捕获除法错误的代码一样,通过缩进来控制比会被执行的代码块。
在必执行的代码块中,常常存放着一些清理资源、关闭文件等功能的语句。例如,上面的例子在 try
中打开了相应的文本,如果没报错,此时就需要在使用后将 open
的资源释放,可以通过下面的语句:
if 'file' in locals():
file.close()
上述语句通过判断局部作用域是否有 file
变量,如果有说明文件被打开,此时则把相应的文件关闭。
3. 使用assert进行断言
类似于运行 raise
会抛出错误一样,断言 assert
可以检验相应的约束是否成立,如果成立,则代码继续,如果不成立,则抛出AssertionError
错误。
如下例子:
def validate_age(age):
assert age >= 0, "年龄不能为负数"
assert age < 150, "年龄无效"
try:
validate_age(-10)
except AssertionError as e:
print("断言失败:", str(e))
上述例子在 validate_age
函数中增加了两个断言,即传入的年龄需要大于 0 且小于 150,否则抛出 AssertionError
错误。当输入 -10 时,第一个断言判断不通过,返回报错信息,信息被 except
捕获并做出相应处理。
4. 总结
由于Python不是强类型语言,在定义变量的时候并不申明变量类型,此外,有时候我们程序进行一定的操作之前需要检查输入(输出)数据的正确性,这时候我们会写一些防御性代码进行判断,以更好地处理错误和异常情况,增加代码的健壮性和可靠性。
但同时需要注意的是,大量的异常判断有时候会降低代码性能,因此需要权衡进行异常判断的关键位置。