没有学不会的python--异常处理

没有学不会的python


什么是异常?

按照字面的意思就是不正常,翻译成代码层面的意思是,由于程序员不理解某段代码真正的用法,没有正确使用该代码,或者对业务逻辑的思考不全面,导致个别程序缺陷,从而出现异常。

异常对于代码来说,是正常的,没有人能写出完全没有Bug的代码来,就像世界上没有不生病的人,所以,出现了异常,我们要去处理它,不可任由它发展,并且一些比较明显、不应该犯的异常,我们在遇到过之后,必须牢牢记住,防止以后再犯类似的错误。 

 

不处理异常的后果

有的同学会说,产生了异常,我不去处理它行不行,对我的程序有没有什么影响。有这种想法的同学,大部分都是嫌弃处理异常很麻烦,不愿多花篇幅去处理异常,觉得没必要。

那我就问一下,假如你病了不去治行不行哦?

你肯定不用想就会说,不行!!!我的命很金贵。哈哈对,人都应该珍惜自己。但我也常常想起一段话,就是:

人应该对代码怀有敬畏之心。

如果你不敬畏代码,代码就会错乱,如果是你自己的测试代码什么的当然没事,错了就错了。但想想,假如你的项目被几百万个人用着,产生了一个异常导致整个系统不能用,你会有什么后果?用屁股想也知道,收拾包袱走人了吧?

这是从实际生产中来讨论的后果,在代码层面来说,有两种类型的异常,一种是error,一种是exception。顾名思义,error就是错误,就是不应该发生的事情。exception就是异常,异常就是不正常,这种不正常我们应该去处理它,从而保持整个程序的正确运行。

异常不处理的后果就是,运气好的话,只是最后的结果不正确,但你可能发现不了,这种情况下你就被程序骗了,毕竟你都不知道有这么个异常。最坏的结果是程序崩溃,强制停止运行。

 

 

捕获异常

捕获异常的意思就是对产生异常进行处理,从而使程序往正确的方向发展。捕获异常的语法如下:

try:
 ....
except exception as err:
 ....
else:
 ....
finally:
 ....

上述的语句意思是,执行try块的代码,如果产生异常,就会跳转到except块执行,如果没有产生,就跳到else块执行。最后,不管有没有发生异常,finally块都是一定会执行的。

但实际开发中,finally块和else块不是必须的,可以不需要。现在我们来看看例子。

最简单的捕获异常示例:

# 异常处理

dict_1 = {"sex": "male", "age": 12}

dict_1["name"]

猜猜上面有没有捕获异常?哈哈哈。当然没有,看下输出:

Traceback (most recent call last):
 File "D:/code/python/blog/main.py", line 5, in <module>
 dict_1["name"]
KeyError: 'name'

我写这个例子肯定不是白写的,也不是为了调侃大家,我是教大家一种方法,当你不知道怎么去写应该捕获哪个异常的时候,可以通过bug来得到。上述的例子报的是keyError,所以我们要处理的就是这个异常!明白了不?

最简单的捕获异常:

# 异常处理

dict_1 = {"sex": "male", "age": 12}

try:
 dict_1["name"]
except KeyError:
 pass

上述是最简单的捕获异常的例子,虽然except语句块什么都没有做,但是,这样子的作用是,程序能走到结尾,不会崩溃,这就是捕获异常的作用。

上面我们也看到了,我们只用了try和except块,else块和finally块都没用。

再看一个完整的例子:

# 异常处理

dict_1 = {"sex": "male", "age": 12}
name = None
try:
 name = dict_1["name"]
except KeyError:
 name = "huang"
else:
 print("else " + name)
finally:
 print("finally " + name)

输出结果:

finally huang

上述的例子可以看到name在try块中从dict_1获取值,但是没有获取到抛出异常,进入了except块,然后获得了一个字符串,然后跳到finally块执行输出。

异常顺序的重要性

捕获异常的顺序很重要,因为异常的抛出规则是冒泡抛出,即它是按except块的顺序一个个去匹配的,当匹配成功了就不往下匹配了。如果没有其它编程语言基础的同学可能会说,这有什么关系呢?

肯定有关系,因为另一个原因,python也是一门面向对象的编程语言,因此,也有类。在这里说这个还早,关于类是放在后面来讲的。这里简单提一下,类之间是有继承关系的,一个类可以是别的类的字类,也可以是别的类的父类。当抛出异常的时候,如果抛出的异常类型匹配到该异常类型或者该异常类型的父类型,都会认为匹配成功,就不往下执行了。

打个比喻,快递给你寄快递,寄到你家的时候喊你,你没听到,你爸听到了,你爸也可以代你收快递,同时快递员的快递也送到了,不会继续给你送了。

举个例子: 
因为LookupError是KeyError的父类,因此KeyError应该处于比较前的位置,否则,永远也无法捕获到KeyError。看代码:

# 异常处理

dict_1 = {"sex": "male", "age": 12}
name = None
try:
 name = dict_1["name"]
except LookupError:
 print("LookupError")
except KeyError:
 print("KeyError")

输出结果

LookupError

上述代码因为LookupError在前,所以导致KeyError永远无法执行。这样如果在实际开发中,一些本应该在keyError异常处理的代码逻辑就无法得到执行,而程序又不报错,最后只能是输出结果有问题。

正确的写法是:

# 异常处理

dict_1 = {"sex": "male", "age": 12}
name = None
try:
 name = dict_1["name"]
except KeyError:
 print("KeyError")
except LookupError:
 print("LookupError")

不好的习惯

很多初学者甚至很多有多年开发经验的程序员,可能因为不知道代码块会抛出什么异常,或者是嫌麻烦亦或者是根本没意识,总会将所有的代码都只被一个异常处理,即Exception。因为,这个异常是处理系统异常之外的所有异常的父类,因此所有的异常都会被它匹配到,于是很多人就这样子写代码:

# 异常处理

dict_1 = {"sex": "male", "age": 12}
name = None
try:
 name = dict_1["name"]
except Exception as err:
 print("exception")

上面的程序看上去没什么问题,异常也被捕获了,系统也不会崩溃了。但是,我们再看一下,假如是这样的代码:

# 异常处理

dict_1 = {"sex": "male", "age": 12}
name = None
try:
 a = 10 / 0
 name = dict_1["name"]
except Exception as err:
 print("exception")

输出结果:

exception

各位同学看的出来是try语句块里面的哪句抛出的异常吗?看不出来是吧?但我能看出来,不过我是从开发经验判断出来的,如果上面的代码是很长的一段,我就看不出来了。

由于我们没法定位具体抛出了什么异常,也就没法针对特定的异常进行相应的业务逻辑处理。所以到这里都明白了吗?上面这种做法不可取!

抛出异常

前面讲到的都是捕获异常,也就是处理系统抛出来的异常。那么到这里可能就会问,只有系统能抛出异常吗?我们能不能抛出异常?答案是肯定的,如果我们不能抛出异常,那么很多现实存在的问题将得不到解决。就拿实际开发中来说,往往一个项目是小组成员之间互相合作完成的,那么就会涉及到你调用我的函数或者我调用你得函数。比如你写了个函数,然后你期待别人要那样才可以调用你得函数,但是那个人没有按照你的预期来调用你的函数,你就会抛出一个异常给他,告诉他哪里错了。然后他就会按照返回的异常进行修改处理,从而使整个程序得以运转。

抛出异常非常简单,只需要一个raise关键词即可,raise后面跟着的是你要抛出的异常类型。比如:

# 异常处理

try:
 raise KeyError
except KeyError:
 print("抛出异常")

上面的代码就是抛出keyError异常并捕获处理,不过不同的是,这次这个异常是我们自己抛出去的,不是因为代码出问题抛出去的,因此对于抛出异常,我们有了主动权。现在你学会了怎么抛出异常了吗?以后如果遇到代码中有不符合你业务逻辑要求的变量值或者环境之类的问题时,你大可以抛出异常告诉别人哪里有问题,这样人家就不会天天捧着代码找你定位问题,这样大家都节省时间。

自定义异常

到了这里,基本上来说,你可以处理别人或者系统抛出来的异常,你也可以抛出异常给别人处理。只不过还欠缺最后一步,就是定义自己的异常,前面讲到的几个异常都是系统异常,也即是python内置的异常类型,但往往系统异常并不适合我们,如果我们也抛出这些异常,将会给捕获异常的那个人带来很大的麻烦。所以根据实际情况来说,我们应该自定义异常才对。

实际上来说自定义异常需要有类的相关知识,我们这系列文章还没讲到。如果学过类的同学,会更好理解,没有学过的也没关系,因为我这里只讲最简单的自定义异常,抛开多余的细节,我们直蹦核心,争取以最简单的方式理解最核心的内容。

下面是自定义异常的示例代码:

class MyException(Exception):

 def __init__(self):
 self.message = "这是我抛出来的异常"

 def __str__(self):
 return self.message


try:
 raise MyException
except MyException as err:
 print(err)
输出结果
这是我抛出来的异常

上面的class表示类的意思,即定义了MyException是一个类,然后(Exception)表示继承的意思,即MyException继承自Exception,然后我们重写了init方法和str方法,前者是初始化函数,就是用来初始化一个类的属性的,后者是字符串描述函数,我们调用print()函数的时候,如果输出的是一个对象,其实调用的就是这个对象的字符串描述函数。

所以我们捕获异常是调用print(err)实际上是调用了str函数,该函数返回了message属性值,即我们自定义的错误消息。这样别的人看到你抛出来的异常描述,就知道发生什么问题了。

到了这里,小伙伴们需要自己做个练习才能巩固,就是按照自定义异常的示例代码,自己写一个你自己的自定义异常,

好了,这篇文章就结束了,让我们期待下一篇,约定你哦!喜欢我的系列文章的话,可以关注我的主页也可以关注我的公众号。

 

 

如果你有什么问题想要反馈或者联系我,可以加我。 

 

最后给大家送上我的法宝:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值