写程序,写多了,就容易出bug,当然也容易出现Exception异常!
什么是错误与异常Exception?
这个跟语法错误不一样,语法错误编译器会直接提示处理(在程序运行之前)。 比如IndentationError,这种是语法级别的异常,它也是Exception的子类)
出bug,则是在运行过程中产生超乎意料的处理结果,逻辑处理错误,但是这类错误通常不会让程序中断,往往是反应在最后的结果输出中,发现偶然几次运行跟预期的不一样,或者偶然几个输入的输出结果不符合总体运行规律。
异常就是程序中间出现 值/操作 不在系统内设定预期值内,在程序执行过程中产生的错误(不合法的数据类型,超预期设定的像磁盘/网络/文件故障等等),而且错误会被程序抛出,中止后续运行。 所以有时候我们也称呼异常为:运行时错误!
可能小白不太懂,比如下面一个现象,有点高能:
小白打算申请跟Python编译器登记结婚? 无法理解的,称之为错误!
小白跟一个萌妹子结婚,这是正常情况(没有异常也没有错误)
小白跟一个同性结婚,这是属于异常情况(在现今社会没有被广泛认可,但是确实存在。)
大概的意思就是,编程里面也是存在‘标签化’的语义。
异常 和 错误 本身就是超越常规程序运行应该处理的结果,我们习惯与把一些表现归类为错误,习惯与另一部分归类为异常。
文末会展示异常的结构树,会再次谈到异常跟错误的关系。
展示一下异常
直接复制下方代码到python REPL终端:
result = 1 / 0 print("result=", result)
运行效果如下:
看到了吗?
这就是异常,这里的异常是:ZeroDivisionError, 错误消息:division by zero(除零错误!)
错误与异常的关联和更多展示
常见的异常很多,像下面的几种,都非常常见。
ModuleNotFoundError : 通常出现在import leixuewei #导入一个库的时候,这个库不存在则抛出异常!
KeyError: 出席在dict类型的数据取某个键值的时候,比如直接定义一个dict类型的数据,不放入任何键值就取‘leixuewei’这个key,那就会抛出这个错误。
IndexError: 这种就是访问数组或者list列表数据的时候,超出下标,明明一个list就只有2个元素,硬要取第三个元素的时候报错。
ArithmeticError: 上面展示的数值除以0,抛出的异常就是一种,还包括了OeverflowError, FloatingPointError等。
这些错误都非常直观。
这里我们可以打开PyCharm找到Exception类,使用‘find usage’ 查看这个类的子类。
记住这些异常,其实意义不大。
我们编程更多是需要知道异常的链条和如何使用异常,知道把握这些异常的关联: 继承关系。
下面,我们双击RuntimeError, 马上就发现这个RuntimeError class 后面紧跟了一个NotImplementedError子类。
那么异常的根源在哪里?
我们继续查找Exception 的父亲BaseException,如下图:
开发中如何运用异常
灵活运用断言异常,它就挺适合在程序中进行预判的。当然最好的方式是,补充unittest(单元测试)这个在学委之前文章中提到了。
AssertionError 断言异常
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2021/10/23 12:32 下午 # @Author : LeiXueWei # @CSDN/Juejin/Wechat: 雷学委 # @XueWeiTag: CodingDemo # @File : assertdemo.py # @Project : hello def double(v1): result = v1 * 2 assert result == v1 + v1 return result def double_v2(v1): result = v1 * 2 assert result == v1 + 2 return result print(double(3)) print(double_v2(3))
运行效果如下:
这里我们写了一个double(双倍函数),出入一个数字,内部计算应该是2*数字,我们也预期结果为: 数字 + 数字
这种断言异常可以在程序中灵活使用,学委建议不要一下子写几百行代码,而且实现一段逻辑代码的时候适当加上。
比如在做算法类型的题目的时候,加上这个可以有效的辅助程序的完成:有效的中间产生结果校验。
异常处理
Python 提供了语法支持方便我们针对程序异常,按情况进行处理,语法解释如下:
try: #运行可能出现异常的函数abc abc() except Exception as e: #上述函数abc运行出现异常 运行一下异常补救的代码或者记录异常 else: #abc函数没有发生异常 运行确认没有异常的代码 finally: #不管有没有异常 print(“总会被执行打印!”)
这里学委写了一段示例代码,可以直接复制运行:
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2021/10/24 1:54 下午 # @Author : LeiXueWei # @CSDN/Juejin/Wechat: 雷学委 # @XueWeiTag: CodingDemo # @File : trycatchdemo.py # @Project : hello def divide(a, b): return a / b result = 100 has_error = False try: data = divide(1, 0) #运行除0函数,会抛出异常 except Exception as e: print("error is %s" % e) has_error = True else: print("there is no exception") finally: print("always run ") if not has_error: result += data print("final value %s" % result)
异常捕捉,可以让程序跳过指定异常,继续执行。
如语法介绍那样,正确运行如下图:
使用try-except语句这不是必要的。
通常我们在应对外部接口或者外部程序调用,外部资源整合的时候,用这个可以有效的保护本程序,跳过外部(别人的)程序错误,继续执行程序,仅此而已。
可以在except 块内放置一些错误处理或者补救代码,比如divide函数还成对等的函数,然后继续后续程序处理。
定义自己的异常,并抛出!
当我们开发一个大型应用的时候,比如Django,我们发现运行这个框架开发的时候,有时候会出现一写Django自带的异常。
这种要如何做?
定义用户自己的异常
通常都是继承RuntimeError类,这类型的异常肯定是运行时异常。
如下:
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2021/10/24 2:18 下午 # @Author : LeiXueWei # @CSDN/Juejin/Wechat: 雷学委 # @XueWeiTag: CodingDemo # @File : userexception.py # @Project : hello class DotNotCopyError(RuntimeError): def __init__(self, arg): self.args = arg def copy(): print("抄袭!") raise DotNotCopyError("某某抄袭了学委的文章!") copy()
非常简单,这里也展示了如何抛出异常,我们使用raise关键字加上异常的实例即可。
运行效果如下:
raise也可以抛出其他内置的异常,可以根据需要在程序中使用。比如程序中检测网络情况,预先抛出异常,帮助运维及早发现外部依赖故障。这个请根据实际情况编写。
延伸思考 - SyntaxError居然也是异常的子类!
但是这里我们看到SyntaxError也是异常Exception,错误不是跟异常不一样吗?怎么都是同个父亲:Exception。所以严格上来说或Exception不能称为运行时异常,而是部分Exception的子类为运行时异常,因为SyntaxError属于编译解析器解析代码的时候发现的语法错误。
而且SyntaxError是不能够被程序捕捉的。
从这里看,Python的异常机制设计跟Java相比显得比较凌乱了!在Java中异常的根部是Throwable,然后延伸出Error和Exception两个派系,像Error是不能被捕捉,这方面比较清晰。
总结
python的异常,学委总结如下:
今天1024,学委在此祝愿大家程序不出bug,然后拿捏一切异常!