1 错误
1.1 概念
- 无法通过其他代码进行处理问题
1.2 类型
- 语法错误(解析错误)
比如定义函数关键字写错;
# 错误写法
dfe test():
pass
# 正确写法
def test():
pass
这种错误可以通过 IDE 或者 解析器 给出的提示进行修改
- 逻辑错误
语法层面没有问题,而是业务逻辑设计出现问题
# 判断是否成年错误
if age < 18:
print("已成年")
这种错误,IDE 或解析器是无法检测出来的,只有通过业务上的代码测试进行排查
2 异常
2.1 概念
- 多指程序在执行的过程中,出现的未知错误;
- 语法和逻辑都是正确的;
- 可以通过其他代码进行处理修复;
期望用户输入的是数字,而用户输入字符串,类型异常
age = input("请输入年龄")
if int(age) >= 18:
print("该用户已成年")
除数为零异常
def devide(x, y):
return x / y
其他:列表或字典,在使用的过程中,出现的索引或者 key 的错误等
3 错误和异常区别
- 异常可以通过代码进行修复,而错误不行
4 常见的系统异常
- 除零异常:ZeroDivisionError
1 / 0
- 名称异常:NameError
print(name)
- 类型异常:TypeError
"1" + 2
- 索引异常:IndexError
lis = [1, 2]
lis[2]
- 键异常:KeyError
dic = {"name":"fkm", "age":22}
dic["aaa"]
- 值异常:ValueError
int("abc")
- 属性异常:AttributeError
name = "fkm"
print(name.xx)
- 迭代器异常:StopIteration
it = iter([1, 2])
print(next(it))
print(next(it))
print(next(it))
- 系统异常类继承树:
-| BaseException (所有内建的异常的基类)
-| KeyboardInterupt (当用户点击中断键(通常 Ctrl+ C)时引发)
-| SystemExit (由 sys.exit()函数引发,当它不处理时,Python 解析器退出)
-| GeneratorExit (当调用一种 generator 的 close()方法时引发)
-| Exception (所有内置的,非系统退出异常都是从该类派生的,应该从该类派生所有用户定义的异常)
-| (all other current built-in exception)
5 解决异常
5.1 预防
- 添加容错代码
- 弊端:容错代码不属于主业务逻辑,过多容错代码会造成代码逻辑混乱,业务线流程不清晰
# 抛异常代码
def devide(x, y):
return x / y
# 容错处理
def devide(x, y):
if y != 0:
return x / y
print(“除数不能为0”)
return 0
5.2 解决
5.2.1 try except 方案
- 简单解决
try:
print(name)
except NameError:
print("名称有问题,请检查")
print(123123)
>>>>打印结果
名称有问题,请检查
123123
-
使用 try except 完整结构处理
- 简单指定异常捕获
try:
1 / 0
except ZeroDivisionError:
print("ZeroDivisionError")
print("程序继续运行到最后")
>>>>打印结果
ZeroDivisionError
程序继续运行到最后
- try 语句没有捕获到异常,执行顺序为: try 代码段 -> else代码段 -> finally代码段
try:
print("无异常")
except ZeroDivisionError:
print("except - ZeroDivisionError")
else:
print("else - 没有异常时执行")
finally:
print("finally - 有无异常都会执行")
print("程序继续运行到最后")
>>>>打印结果
无异常
else - 没有异常时执行
finally - 有无异常都会执行
程序继续运行到最后
- try 有 捕获到异常,执行顺序为: try 代码段 -> except区代码段 -> finally代码段
try:
1 / 0
except ZeroDivisionError:
print("except - ZeroDivisionError")
else:
print("else - 没有异常时执行")
finally:
print("finally - 有无异常都会执行")
print("程序继续运行到最后")
>>>>打印结果
except - ZeroDivisionError
finally - 有无异常都会执行
程序继续运行到最后
- try块中,即使有多个异常,都只会捕获首个异常
try:
1 / 0 # 首个被处理异常
print(name) # 后面异常不处理
except ZeroDivisionError:
print("except - ZeroDivisionError")
except NameError:
print("except - NameError")
else:
print("else - 没有异常时执行")
finally:
print("finally - 有无异常都会执行")
print("程序继续运行到最后")
>>>>打印结果
except - ZeroDivisionError
finally - 有无异常都会执行
程序继续运行到最后
- as 获取对应异常类型值
try:
1 / 0 # 首个被处理异常
print(name)
except ZeroDivisionError as zde:
print("except - ZeroDivisionError", zde)
except NameError as ne:
print("except - NameError", ne)
else:
print("else - 没有异常时执行")
finally:
print("finally - 有无异常都会执行")
print("程序继续运行到最后")
>>>>打印结果
except - ZeroDivisionError division by zero
finally - 有无异常都会执行
程序继续运行到最后
try:
# 1 / 0 # 首个被处理异常
print(name)
except ZeroDivisionError as zde:
print("except - ZeroDivisionError", zde)
except NameError as ne:
print("except - NameError", ne)
else:
print("else - 没有异常时执行")
finally:
print("finally - 有无异常都会执行")
print("程序继续运行到最后")
>>>>打印结果
except - NameError name 'name' is not defined
finally - 有无异常都会执行
程序继续运行到最后
- 合并异常处理
try:
# 1 / 0 # 首个被处理异常
print(name)
except (ZeroDivisionError, NameError) as zde:
print("except - ZeroDivisionError", zde)
# except NameError as ne:
# print("except - NameError", ne)
else:
print("else - 没有异常时执行")
finally:
print("finally - 有无异常都会执行")
print("程序继续运行到最后")
>>>>打印结果
except - ZeroDivisionError name 'name' is not defined
finally - 有无异常都会执行
程序继续运行到最后
- 如果异常名称不确定,但又想捕获,except 中填入异常类型为 Exception 即可(含多态的概念,因为 Exception 是内置异常的父类)
try:
# 1 / 0 # 首个被处理异常
print(name)
except Exception as ex:
print("except - Exception", ex)
else:
print("else - 没有异常时执行")
finally:
print("finally - 有无异常都会执行")
print("程序继续运行到最后")
>>>>打印结果
except - Exception name 'name' is not defined
finally - 有无异常都会执行
程序继续运行到最后
5.2.2 with 语句方案
- 作用
适用于执行某段代码 A 之前,进行预处理,执行代码 A 结束后,进行清理操作
不管出现了什么异常,最终都要执行一些清理操作 - 语法
with context_expression [as target(s)]:
with-body
-
执行流程
- context_expression返回一个上下文管理器对象
- 调用上下文管理器的
__enter__
方法,如果写了 as target 语句,则把__enter__
方法的返回值,赋值给 target - 执行语句体
- 执行上下文管理器的
__exit__
方法
上下文管理器对象:是指实现了
__enter__
方法 和__exit__
方法的对象
事例一:异常影响后续操作无法执行
文件读取操作的正常步骤
1 打开文件
f = open("xxx.jpg", r) # 正常读取二进制文件是 rb
2 读取文件
f.readlines() #读取操作抛异常,则会导致后续的关闭操作无法执行
3 关闭文件
f.close()
事例二:使用 try finally 捕获异常后仍然执行后续操作
try:
f = open("xx.jpg", "r") #默认存在 xx.jpg 文件,能够被正常打开
f.readlines()
except Exception as e:
print(e)
finally:
print("xxxx")
f.close()
事例三:使用 with 语句
with open("xx.jpg", "r") as f:
f.readlines()
# 上述代码抛异常后,会执行`__exit__`方法,而此时 exit 对应的就是 f.close()
5.2.3 自定义上下文管理器
方案一:通过类实现上下文管理器
- 通过让自定义对象实现
__enter__
和__exit__
方法,结合 with 语句创建对应实例即可成为上下文管理器
学习点:
1、管理器需要实现的方法
2、了解执行 enter body exit 执行顺序
class Test:
def __enter__(self):
print("enter")
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
with Test():
print("body")
>>>>打印结果
enter
body
exit
- 在 body 块中使用上下文管理器形参,(t 即为方法 enter 的返回的实例对象)
学习点:
1、as 关键字使用
2、enter 方法返回值传递
class Test:
def __enter__(self):
print("enter")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
with Test() as t: #t 即为方法 enter 的返回的实例对象
print("body", t)
>>>>打印结果
enter
body <__main__.Test object at 0x103b56630>
exit
- body 块中产生异常时,会将异常信息传递到 exit 方法中
学习点:
1、exit 方法参数意义
exc_type:异常类型
exc_val:异常值
exc_tb:异常追踪器
class Test:
def __enter__(self):
print("enter")
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
print(self)
print("exc_type", exc_type)
print("exc_val", exc_val)
print("exc_tb", exc_tb)
with Test():
print("body")
1 / 0
>>>>打印结果
enter
Traceback (most recent call last):
body
File "/Users/xxx/Desktop/PythonProject/AreaDefine/面向对象案例.py", line 950, in
exit
<__main__.Test object at 0x1046fe9b0>
exc_type
exc_val division by zero
exc_tb
1 / 0
ZeroDivisionError: division by zero
- exit 方法内部消耗掉由body 产生的异常
学习点:
1、exit 方法通过 return True 内部消耗掉由body 产生的异常,让其不将异常再往外抛
2、如果return False 或 没有 return ,异常仍然会往外抛
class Test:
def __enter__(self):
print("enter")
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
print(self, exc_type, exc_val, exc_tb)
return True
with Test():
print("body")
1 / 0
>>>>打印结果
enter
body
exit
<__main__.Test object at 0x10d9e6630> division by zero
- 通过 traceback 模块,使用 exit 方法内的 exc_tb 对象
学习点:
1、提取ext_tb内容,返回的是一个追踪信息列表
class Test:
def __enter__(self):
print("enter")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
import traceback
print(traceback.extract_tb(exc_tb))
return True
with Test() as x:
1 / 0
>>>>打印结果
enter
exit
[>]
方案二:借助 contextlib 模块,通过生成器快速实现上下文管理器
-
@contextlib.contextmanager
学习点:
1、函数生成器语法
2、生成器的上下文管理器,如何区分上文和下文以及返回值
3、生成器的上下文管理器的返回值执行时间与类实现中的 enter 方法返回的时机区别(生成器的上下文管理器返回值是执行 body 时才通过 yield 返回的,此时上文块已经执行完毕)
import contextlib
@contextlib.contextmanager
def test():
print(1)
yield "kkkk"
print(2)
with test() as t:
print(3, t)
>>>>打印结果
1
3 kkkk
2
- 通过生成器的上下文管理器,实现代码解耦
# 常见代码
x = 1
y = 0
try:
x / y
except ZeroDivisionError as e:
print(e)
a = 1
b = 0
try:
a / b
except ZeroDivisionError as e:
print(e)
>>>>打印结果
division by zero
division by zero
解耦代码
import contextlib
@contextlib.contextmanager
def te():
try:
yield
except ZeroDivisionError as e:
print(e)
x = 1
y = 0
with te():
x / y
a = 1
b = 0
with te():
a / b
>>>>打印结果
division by zero
division by zero
-
contextlib.closing
,这个函数,让一个拥有 close 方法但不是上下文管理器的对象变成上下文管理器
通过类上下文管理器,让实例每次调用 t 方法后都执行 close 方法
class Test:
def t(self):
print("tttttt")
def close(self):
print("资源释放")
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
with Test() as t_obj:
t_obj.t()
>>>>打印结果
tttttt
资源释放
通过contextlib.closing 处理(必须实现了 close 方法)
class Test:
def t(self):
print("tttttt")
def close(self):
print("资源释放")
import contextlib
with contextlib.closing(Test()) as t_obj:
t_obj.t()
>>>>打印结果
tttttt
资源释放
-
contextlib.nested
, Python2.7之前:完成多个上下文管理器的嵌套
使用 with 的嵌套
with open("1.jpg", "rb") as from_file:
with open("2.jpg", "wb") as to_file:
from_contents = from_file.read()
to_file.write(from_contents)
推荐写法,Python2.7之后
with open("1.jpg", "rb") as from_file, open("2.jpg", "wb") as to_file:
from_contents = from_file.read()
to_file.write(from_contents)
Python2.7之前
import contextlib
with contextlib.nested(open("1.jpg", "rb"), open("2.jpg", "wb")) as (from_file, to_file):
from_contents = from_file.read()
to_file.write(from_contents)
6 手动抛异常
- 手动抛异常意义在哪?
- 如何在自定义函数中向外界抛异常?
- 什么时候内部消化异常,什么时候将异常抛给外界?
def set_age(age):
if age <= 0 or age > 150:
# print("数值错误") # 不应当内消错误
raise ValueError("值错误") # 框架开发人员应当将错误外抛,以使外界作出合适处理
else:
print("年龄设置为", age)
# 外界使用
try:
set_age(-18)
except ValueError as e:
print("x", e)
>>>>打印结果
x 值错误
7 自定义异常
- 异常对象需要继承 Exception、或 BaseException
class LessZeroError(Exception):
def __init__(self, error_msg, error_code):
self.msg = error_msg
self.code = error_code
def __str__(self):
return self.msg + str(self.code)
pass
def set_age(age):
if age <=0 or age > 100:
raise LessZeroError("小于零错误", 502)
else:
print("年龄设置为", age)
try:
set_age(-18)
except LessZeroError as e:
print("x", e)
>>>>打印结果
x 小于零错误502