目录
关于这个系列
1、定义
2、处理异常
3、清理行为
4、自定义异常
《最值得收藏的python3语法汇总》,是我为了准备公众号“跟哥一起学python”上面视频教程而写的课件。整个课件将近200页,10w字,几乎囊括了python3所有的语法知识点。
你可以关注这个公众号“跟哥一起学python”,获取对应的视频和实例源码。
这是我和几位老程序员一起维护的个人公众号,全是原创性的干货编程类技术文章,欢迎关注。
大家需要知道一个事实,那就是,在一个正式的软件项目代码里面,会有较大比例的代码是在处理程序的错误和异常场景。如果你想让你的程序能足够稳定的运行,那么你在写代码时就必须要考虑到可能出现的所有异常,并且处理它们。
我们看看下面这个例子:
# author: Tiger, 关注公众号“跟哥一起学python”,ID:tiger-python
# file: ./13/13_1.py
# 错误和异常
def my_divide(a, b):
x = a / b
return x
print(my_divide(100, 10))
这个程序运行完全正常,看起来没有任何问题。
但是,如果我们修改一下传入的实参,就会报异常:
print(my_divide(100, 0))
我们将第二个实参,也就是被除数,改为0。以我们的数学常识我们知道,除以0,是不被允许的。程序会报异常并且直接结束:
ZeroDivisionError: division by zero
所以,这个简单的程序就是典型的,只实现了主要功能逻辑,而不考虑异常分支的案例。作为一个软件项目来说,稳定性可靠性的重要程度不亚于功能性。
在Python中存在两种错误:语法错误和异常。
语法错误又称为解析错误,它是python解释器在解析代码的时候报的一种错误。比如:
# 语法错误
def func_xxx()
pass
很显然,我们在定义函数的时候少了一个冒号。解释器在编译时会给我们明确的错误提示:
File "D:/跟我一起学python/练习/13/13_1.py", line 12
def func_xxx()
^
SyntaxError: invalid syntax
它会列出出错的模块路径,以及出错的代码行行号。同时它会用一个箭头符号^,指向出错的具体地方,并且抛出语法错误的提示invalid syntax。
异常,则是在程序运行过程中产生的错误。比如我前面例子中提到的ZeroDivisionError,就是属于异常的范畴。
异常并不都是代码的bug,通常异常只是代码逻辑的一种特殊场景,它需要我们特殊处理。如果不处理,这个异常会被抛给Python解释器,解释器就会将程序强制退出。
Python中通过try语句来处理异常,其语法如下:
try:
# 可能会抛出异常的代码段
except Exception1:
# 对异常Exception1的处理except Exception2 as e2:
# 对异常Exception2的处理,可以使用e2获取异常信息except (Exception1, Exception1,...):
# 对多个异常同时处理
else:
# 没有任何异常发生时的处理
finally:
# 不管有没有异常,都会执行的代码
当try里面的代码段运行时抛出了异常,except会捕捉这个异常,如果except没有捕捉,那么这个异常就会抛给解释器,解释器会强制退出程序。
只有这个异常在except语句的异常列表中,才会被捕捉。Except里面的处理语句可以为空,系统也会认为该异常被处理了。
如果我们不清楚代码段到底会抛出多少异常,我们可以使用except来捕捉异常Exception。因为所有的异常都是从Exception直接或间接派生出来的,所以它可以捕捉所有的异常。但是只捕捉Exception这种做法不被提倡,最好还是去捕捉具体的异常并做相应的处理。
Else里面的代码,在没有任何异常的时候会被执行,这通常可以用来判断try里面的代码段是否执行成功。
Finally里面的代码,在任何情况下都一定会被执行。它通常用来做一些清理工作,比如释放内存、关闭文件等。
我们也可以使用raise关键字抛出异常,下面这个例子中我们抛出python内置的异常,下节我们讲如何自定义异常。
下面我们看一个比较完整的例子:
# 处理异常
def sample_raise(x):
'''
不要关注下面抛出异常的具体含义,
这里仅仅用于演示
'''
if x > 100:
raise MemoryError("x > 100")
elif 50 > x >= 0:
raise ValueError("50 > x >= 0")
elif x < 0:
raise KeyError("x < 0")
else:
return x
try:
sample_raise(-1)
except MemoryError:
print('MemoryError is occured!')
except ValueError as e:
print(f'ValueError, error info is : {e}')
except Exception as e:
print(f'Exception, error info is : {e}')
else:
print(f'else branch input')
finally:
print(f'finally branch input')
输出为:
Exception, error info is : 'x < 0'
finally branch input
当try里面的代码段抛出一个异常后,except是从上往下依次捕捉的,一旦被捕捉到就不会再往下寻找。如果except语句中捕捉了重复的异常,或者我们把父类Exception放在了前面,那很可能会被提前捕捉。
从上面例子我们也可以看到,finally是一定会被执行的,所以它通常用于释放一些资源,完成程序异常时的清理工作。
当程序发生异常时,我们需要做一些清理工作,比如当我们打开了一个文件,如果读取文件时出现异常,那么我们希望在程序退出之前可以把文件关闭。
如果不考虑异常情况,那么我们会这样写这个程序:
# author: Tiger, 关注公众号“跟哥一起学python”,ID:tiger-python
# file: ./13/13_2.py
# 不考虑异常
fp = open("test.txt")
for line in fp:
print(line, end="")
fp.close()
这个代码,如果我们不考虑异常,那么在执行的过程中可能会出现UnicodeDecodeError。原因是因为我们的test.txt中存在非gbk编码的字符。程序遇到异常后就直接退出了,fp无法被正常关闭。
所以,我们需要处理异常,并且确保fp一定会被关闭。前面我们学过finally,知道它是肯定会被执行的,所以我们可以把关闭fp的语句写到finally里面。如下:
# 异常情况下finally资源清理
fp = 0
try:
fp = open("test.txt")
for line in fp:
print(line, end="")
except Exception as e:
print(e)
finally:
print('finally!')
if fp != 0:
fp.close()
而更加pythonic的写法,是使用with语句,它对于预定义了清理行为的对象适用。具体一点,就是能使用的with语句的类对象,一定是定义了__enter__()/__exit__()这两个成员方法的。
object.__enter__(self)
进入与此对象相关的运行时上下文。with语句将将此方法的返回值绑定到语句的AS子句中指定的目标(如果有设置的话)
object.__exit__(self, exc_type, exc_value, traceback)
退出与此对象相关的运行时上下文。参数描述导致上下文退出的异常。如果上下文运行时没有异常发生,那么三个参数都将置为None。
如果有异常发生,并且该方法希望抑制异常(即阻止它被传播),则它应该返回True。否则,异常将在退出该方法时正常处理。
请注意, __exit__()方法不应该重新抛出传入的异常。
File这个对象刚好是定义了这两个成员方法的,所以它可以使用with语句。
# with语句,预清理行为
with open("test.txt") as f:
for line in f:
print(line, end="")
with在对象抛出异常时,会自动调用__exit__方法。File对象的__exit__方法里面会关闭文件。
所以,对于这种定义了清理行为的对象,我们采用with会更加方便。
异常都是直接或者间接继承至Exception类,我们当然可以自己定义一个自己的异常类,只要将其父类指定为Exception类即可。
# author: Tiger, 关注公众号“跟哥一起学python”,ID:tiger-python
# file: ./13/13_3.py
# 自定义异常
# 以下例子用于计算体重指标BMI,当达到肥胖指标,抛出一个自定义的异常
class WeightError(Exception):
def __init__(self, bmi, desp_str):
self.bmi = bmi
self.desp_str = desp_str
def __str__(self):
return f'{self.desp_str}, bmi: {self.bmi}'
def get_bmi(height, weight):
bmi = round(weight / (height ** 2), 2)
if 24 <= bmi <= 27.9:
raise WeightError(bmi, '过重')
elif bmi > 28.0:
raise WeightError(bmi, '肥胖')
if __name__ == '__main__':
try:
get_bmi(1.65, 70)
except WeightError as e:
print(e)
finally:
print('finally!')
输出为:
过重, bmi: 25.71
finally!