Python基础36-异常处理(错误和异常)

Python基础-异常处理

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 方案

  1. 简单解决
try:
    print(name)
except NameError:
    print("名称有问题,请检查")

print(123123)

>>>>打印结果
名称有问题,请检查
123123
  1. 使用 try except 完整结构处理


    Python基础36-异常处理(错误和异常)_第1张图片
    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 语句方案

  1. 作用
    适用于执行某段代码 A 之前,进行预处理,执行代码 A 结束后,进行清理操作
    不管出现了什么异常,最终都要执行一些清理操作
  2. 语法
with context_expression [as target(s)]:
    with-body
  1. 执行流程


    Python基础36-异常处理(错误和异常)_第2张图片
  • 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

你可能感兴趣的:(Python基础36-异常处理(错误和异常))