20. 异常处理

閱讀時間約 20 分鐘
raw-image

Hi,大家好。我是茶桁。

在我们日常使用Python或者其他编程语言的时候,不可避免的都会出现报错和异常。那么,我们今天就来谈谈异常。

什么是异常?

异常异常,根据名字简单理解,那就是非正常,也就是没有达到预期目标。

异常呢,其实就是一个事件,并且这个异常事件在程序的运行过程中出现,会影响程序的正常执行。而一般来说,异常被分为两种:

  1. 语法错误导致的异常
  2. 逻辑错误导致的异常

比如:


varlist = [1, 2, 3]
print(varlist[3])

---
IndexError: list index out of range


这个时候,系统抛出了异常,提示我们列表索引超出范畴。

这里我们需要知道,「异常」在Python中实际上也是一个对象,表示一个错误。当我们的程序无法继续正常进行时,就会被抛出。

我们来完整的看看这个报错信息:


---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Cell In[2], line 2
1 varlist = [1, 2, 3]
----> 2 print(varlist[3])

IndexError: list index out of range


Python在遇到异常之后,首先会给出一个「错误回溯」, 然后给出具体哪一句代码出现了问题。

然后在最后给出异常分类和解释。那么IndexError告知我们,这是一个「索引错误」,并且给出了具体的描述「列出索引超出范围」。其中IndexError是我们的异常类, list index out of range是我们的异常信息。

在程序运行过程中,会出现各种各样的异常类,常见标准异常类,我放在最下面作为一个附录。

如何处理异常

可预知

如果错误发生的情况是我们可以预知的,那么就可以使用流程控制进行预防处理。比如,两个数字的运算,其中一个不是数字,运算就会出错,这个时候就可以判断来预防:


n2 = '3'
if isinstance(n2, int):
res = 10+n2
print(res)
else:
print('非整型。')

---
非整形


在这一段代码中,我们使用isinstance方法来检测第一个参数是否是第二个参数的所属类型。这是一个用来检测的方法,返回True或者False。那我们在if中,只有真才会打印结果,假则会打印另外一则消息。

有些小伙伴会想,那既然知道不是整型就会出错,那前面限制传如整型不就好了,干嘛还要费劲去做非整判断。

你要知道,很多时候一个程序的编写和维护并不是单一一个人来做的,即便是一个人在做,也不能完全保证自己某个地方埋下了隐患。那么在每一段代码中,我们对可能预知的情况做妥善的预防是必须的。

不可预知

那可预知的情况我们避免了,可是在我们编写代码的时候,更多的情况是我们自己都不知道我们到底埋了什么雷,哪一段没有遵循规则或者逻辑。那这种情况就是不可预知的。

对于这种不可预知的情况我们该怎么办呢?我们又没办法预先判断。那这种情况下,我们可以使用try...except...语句,在错误发生时进行处理。相关语法如下:


try:
可能发生异常错误的代码
except:
如果发生异常这进入except代码块进行处理

异常被捕获之后程序继续向下执行


我们来看个示例,比如我们之前做过的一个注册、登录练习。其中我们有一段代码是要去读取列表中的所有用户。之前我们的练习中,有提到过文件不存在的情况,所以我们使用了a+的方法,当文件不存在的时候,就新建。

那么现在,我们假设我们就用了r的方法,当文件不存在的时候,一定会报错对吧?这个时候,我们可以使用两种方式来进行处理。

第一种方式,就可以在读取前先判断当前文件是否存在。

第二种方式,就可以使用try...except...在错误发生的时候进行处理。

那么这里,我们用第二种方式来做一下处理:


# 假设我们读取的文件不存在,会发生错误
try:
with open('./data/user5.txt', 'r') as fp:
res = fp.read()
print(res)
except:
print('文件不存在。')

print('程序继续运行...')

---
文件不存在。
程序继续运行...


可以看到,我们准确的捕获了错误,并且之后程序仍然继续往后执行了。

⚠️ try...except... 是在错误发生后进行处理,并不是提前判断。也就是说,错误其实还是发生了。这和if实际上有根本性的区别。

try...except... 详解

首先,我们认识try...except的一个特性,就是它可以处理指定的异常,如果引发了非指定的异常,则无法处理。比如,我们下面人为制造一个异常:


s1 = 'hello'
int(s1)

---
ValueError: invalid literal for int() with base 10: 'hello'


可以看到,我们这一段代码引发了一个ValueError异常。

现在我们来捕获一下, 但是这次,我们为这个异常指定一个异常类再来看看,先看看正常状态下:


try:
s1 = 'hello'
int(s1)
except:
print('程序错误。')

---
程序错误。


接着我们来看指定异常之后:


try:
s1 = 'hello'
int(s1)
except IndexError as e:
print('程序错误。')

---
ValueError: invalid literal for int() with base 10: 'hello'


这里我们指定了一个IndexError的异常类,显然我们之前看到了,程序报错是ValueError异常类,两者并不匹配。所以最后依然还是报错。

那么之前我们谈到过标准的异常类,并且也知道异常实际上也就是一个对象。而我们平时在使用的时候,except实际上就是去这个「标准的异常类」的列表里去查找,如果没有对应的异常类,它依然是无法捕获的。不过大部分时候,我们基本不会遇到标准异常类之外的异常。而这种处理指定的异常类的特性,平时也可以被我们使用。

其中一个使用方式,就是进行多分支处理异常类,不同的异常可以走不通的except进行处理:


s1 = 'hello'
try:
s1[5] # IndexError
except IndexError as e:
print('这里是IndexError', e)
except KeyError as e:
print('这里是KeyError', e)
except ValueError as e:
print('这里是ValueError', e)

---
这里是IndexError string index out of range


是不是和if...elif的分支形式很像?

让我们继续,在我们说指定的异常类中,实际上会有一个万能的通用异常类。那就是Exception


s1 = 'world'
try:
int(s1)
except Exception as e:
print('Exception ===',e)

---
Exception === invalid literal for int() with base 10: 'world'


基本上所有的异常,都可以走到这个异常类。在这段代码中,我们之前记得int(s1)是属于一个ValueError, 但是我们使用Exception依然可以获取到这个错误。可是如果这两种异常类同时被指定的情况下会如何?


s1 = 'world'
try:
int(s1)
except Exception as e:
print('Exception ===',e)
except ValueError as e:
print('ValueError ===', e)

---
Exception === invalid literal for int() with base 10: 'world'


我们看到,就是按照程序的从上至下的顺序在执行。

所以,其实我们可以这样理解,当我们进行多分支异常类+通用异常类的时候,Exception是最后的一个保底。


s1 = 'hello'
try:
# int(s1) # ValueError
s1[5] # IndexError
except IndexError as e:
print('IndexError',e)
except KeyError as e:
print('KeyError',e)
except ValueError as e:
print('ValueError',e)
except Exception as e:
print('Exception',e)

---
IndexError string index out of range


除此之外,try...except是支持else的,当try里的代码顺利执行没有捕获到任何错误之后,还可以走到else之中额外执行分支内的代码:


s1 = 'hello'
try:
str(s1)
print(s1)
except IndexError as e:
print('IndexError',e)
except ValueError as e:
print('ValueError',e)
except Exception as e:
print('Exception',e)
else:
print('try代码块中没有引发异常时,执行')

---
hello
try代码块中没有引发异常时,执行


我们再来了解一下finally, 这个方法是无论是否引发异常都会执行。通常情况下用于执行一些清理工作:


s1 = 'hello'
try:
int(s1)
print('如果前面的代码引发了异常,这个代码块将不在继续执行。。')
except IndexError as e:
print('IndexError',e)
except ValueError as e:
print('ValueError',e)
except Exception as e:
print('Exception',e)
else:
print('try代码块中没有引发异常时,执行')
finally:
print('无论是否引发了异常,都会执行这个代码块')

print('如果上面的代码有异常并且进行了处理,那么后面的代码将继续执行')

---
ValueError invalid literal for int() with base 10: 'hello'
无论是否引发了异常,都会执行这个代码块
如果上面的代码有异常并且进行了处理,那么后面的代码将继续执行


这段代码中,我们引发了一个异常,也被捕获了。但是依然执行了finally内的代码,并且也未影响程序继续往后执行。

在我们平常写代码的过程中还有一种情况,就是我们需要自己制作一个异常信息,然后抛出。这个时候,我们需要用raise, 来主动抛出异常。


try:
raise Exception('发生错误')
except Exception as e:
print('Exception', e)

---
Exception 发生错误


除了上述的异常处理之外,其实还有另外一种方式,是直接判断逻辑是否成立,不成立抛出AssertionError错误。就是使用assert进行断言。它在表达式错误的时候,会直接抛出AssertionError错误,如果表达式正确,这什么都不做。


assert 2 > 3

---
AssertionError:


自定义异常处理类

虽然系统已经给到了很多异常处理的方式,而我们在平时开发中也会经常的使用。但是实际上,很多时候我们都需要一些自己的处理要求。比如说,当异常出现的时候,我们要将异常信息写入日志,在日后我们从日志里查看日常信息或者做数据分析,就是我们最常使用的。

那我们接下来看看,如果做一个异常处理的自定义开发:

再最开始,我们需要归纳一下,我们到底要保存怎样一个格式:


# 日志的基本格式:
- 日期时间, 异常的级别
- 异常信息:引发的异常类别,异常的信息,文件及行号。


在确定了日志格式后,我们可以进入开发了,首先我们需要导入两个所需的库


# 先导入所需模块
import traceback
import logging


让我们先来人为创建一个日常,并用try语句来捕获它:


 int('aaa')

---
ValueError: invalid literal for int() with base 10: 'aaa'


这句代码报了一个ValueError异常类。


try:
int('aaa')
except:
print('在此进行异常的处理')

---
在此进行异常的处理


没问题,我们捕获了异常并且正确的进入了except。那么,我们可以通过traceback模块来获取异常信息, 替换一下打印信息我们来查看一下。


try:
int('aaa')
except:
# 通过traceback获取异常信息
errormsg = traceback.format_exc()
print(errormsg)

---
Traceback (most recent call last):
File "/var/folders/h4/7cr1cmpn7v5b3x20_9wz8m740000gn/T/ipykernel_39689/2534911191.py", line 2, in <module>
int('aaa')
ValueError: invalid literal for int() with base 10: 'aaa'


接下来,就轮到logging模块了。该模块定义了实现用于应用程序和库的灵活事件日志记录系统的函数和类。


logging.basicConfig(
filename = './data/error.log', # 日志存储的文件及目录
format='%(asctime)s %(levelname)s \n %(message)s', # 格式化存储的日志格式
datefmt = '%Y-%m-%d %H:%M:%S'
)


在定义了logging的基本信息之后,我们就可以定义一下将刚才的errormsg写入日志了:


# 写入日志
logging.error(traceback.format_exc())


那么我们完善一下整个代码就是这样:


logging.basicConfig(
filename = './data/error.log', # 日志存储的文件及目录
format='%(asctime)s %(levelname)s \n %(message)s', # 格式化存储的日志格式
datefmt = '%Y-%m-%d %H:%M:%S'
)

# 写入日志
logging.error(traceback.format_exc())


我们需要在异常出发的时候,将错误写入到日志内。那么需要将这段代码放到except中。可是我们总不能每次都写这么长一段代码,那怎么办呢?嗯,没错,我们需要封装一个函数用于多次调用。


def Myexception():
# logging的基本配置
logging.basicConfig(
filename = './data/error.log', # 日志存储的文件及目录
format='%(asctime)s %(levelname)s \n %(message)s', # 格式化存储的日志格式
datefmt = '%Y-%m-%d %H:%M:%S'
)

# 写入日志
logging.error(traceback.format_exc())

# 使用自定义异常处理类
try:
int('bb')
except:
print('在此处进行异常的处理')
Myexception() # 在异常处理的代码块中去调用自定义异常类


然后我们将导入库的方法也写进去,这样在我们需要的时候才会进行导入,顺便,我们将这个函数封装成一个类。就便于更多的文件调用:


# 自定义异常日志处理类
class Myexception():
def __init__(self):
import traceback
import logging

# logging的基本配置
logging.basicConfig(
filename='./error.log',# 日志存储的文件及目录
format='%(asctime)s %(levelname)s \n %(message)s',# 格式化存储的日志格式
datefmt='%Y-%m-%d %H:%M:%S'
)
# 写入日志
logging.error(traceback.format_exc())

# 使用自定义异常处理类
try:
int('bb')
except:
print('在此处进行异常的处理')
Myexception() # 在异常处理的代码块中去调用自定义异常类


这样,一个自定义的获取异常之后写入日常的类就定义好了,我们可以在任意地方导入并调用这个类方法,以便获取以及日后查看整个程序中的异常。

附录

标准的异常类

https://www.runoob.com/python/python-exceptions.html

那么,这节课到这里也就结束了。各位小伙伴,下去以后记得勤加练习。下课。

9會員
62Content count
从基础开始,再到Python,然后是CV、BI、NLP等相关技术。从头到尾详细的教授一边人工智能。
留言0
查看全部
發表第一個留言支持創作者!
茶桁的沙龍 的其他內容
Hi, 大家好。我是茶桁。 在我们之前的课程中,讲解了数据,函数,类,模块以及包。这些基本上已经构成了Python的全部了。 那么,我们在学习Python的包之后,有没有思考过,既然Python有内置模块,我们也可以自己写一些模块来使用,那一定有很多第三方写过相应的模块来供我们使用。那么,这
Hi, 大家好。我是茶桁。 这一段Python之旅怎么样?还算顺利吧? 之前我们都学习了些什么?有基本常识,流程,函数,不同类型的数据以及一些模块对吧?并且还做了一些练习来巩固所学过的内容。 那么今天,我们接着来学习模块。不过今天要学的模块和以往不太一样了,以前我们学习的都是Python内
Hi,大家好。我是茶桁。 不知不觉中,咱们针对人工智能的Python课程已经过去了一半。相信大家这段时间也都有所进步了。 今天这节课呢,我给大家划一个重点。不仅仅是Python,很多语言里都是通用的,而且非常的强大。这就是我们的正则表达式。 说起正则表达式,很多程序员其实对其都不是很重视,但是
Hi, 大家好。我是茶桁。 上一节课最后,我让我家去预习一下日历和时间的相关模块,不知道大家有没有去预习。不管如何,这节课,让我们开始做一个练习:万年历。 没有预习的小伙伴也跟着一起,在本次练习完成的时候,相信你会对这些模块有了初步的了解。 好,让我们开始吧。 首先,我们需要来看看calen
Hi,大家好。我是茶桁。 系统内置模块就是安装完Python解释器之后,系统本身所提供的模块。我知道,咱们之前的课程里有学习系统的内置函数,这个模块和函数不是一个东西。模块这种东西,是需要导入后才可以使用的,比如:json, re, os等等。
Hi,大家好。我是茶桁。 上一节课,我们详细的介绍了文件读写的流程和原理,并用Python进行实际操作了一下。 那么这节课呢,我们利用之前所学的内容,尝试做一个小练习:建立一个登录注册系统。上节课我们在结尾的时候讲练习内容贴了出来,还记得要求吗?  实现功能:  1. 用户输入用户名和密码以及
Hi, 大家好。我是茶桁。 在我们之前的课程中,讲解了数据,函数,类,模块以及包。这些基本上已经构成了Python的全部了。 那么,我们在学习Python的包之后,有没有思考过,既然Python有内置模块,我们也可以自己写一些模块来使用,那一定有很多第三方写过相应的模块来供我们使用。那么,这
Hi, 大家好。我是茶桁。 这一段Python之旅怎么样?还算顺利吧? 之前我们都学习了些什么?有基本常识,流程,函数,不同类型的数据以及一些模块对吧?并且还做了一些练习来巩固所学过的内容。 那么今天,我们接着来学习模块。不过今天要学的模块和以往不太一样了,以前我们学习的都是Python内
Hi,大家好。我是茶桁。 不知不觉中,咱们针对人工智能的Python课程已经过去了一半。相信大家这段时间也都有所进步了。 今天这节课呢,我给大家划一个重点。不仅仅是Python,很多语言里都是通用的,而且非常的强大。这就是我们的正则表达式。 说起正则表达式,很多程序员其实对其都不是很重视,但是
Hi, 大家好。我是茶桁。 上一节课最后,我让我家去预习一下日历和时间的相关模块,不知道大家有没有去预习。不管如何,这节课,让我们开始做一个练习:万年历。 没有预习的小伙伴也跟着一起,在本次练习完成的时候,相信你会对这些模块有了初步的了解。 好,让我们开始吧。 首先,我们需要来看看calen
Hi,大家好。我是茶桁。 系统内置模块就是安装完Python解释器之后,系统本身所提供的模块。我知道,咱们之前的课程里有学习系统的内置函数,这个模块和函数不是一个东西。模块这种东西,是需要导入后才可以使用的,比如:json, re, os等等。
Hi,大家好。我是茶桁。 上一节课,我们详细的介绍了文件读写的流程和原理,并用Python进行实际操作了一下。 那么这节课呢,我们利用之前所学的内容,尝试做一个小练习:建立一个登录注册系统。上节课我们在结尾的时候讲练习内容贴了出来,还记得要求吗?  实现功能:  1. 用户输入用户名和密码以及
你可能也想看
Thumbnail
重點摘要: 1.9 月降息 2 碼、進一步暗示年內還有 50 bp 降息 2.SEP 上修失業率預期,但快速的降息速率將有助失業率觸頂 3.未來幾個月經濟數據將繼續轉弱,經濟復甦的時點或是 1Q25 季底附近
Thumbnail
近期的「貼文發佈流程 & 版型大更新」功能大家使用了嗎? 新版式整體視覺上「更加凸顯圖片」,為了搭配這次的更新,我們推出首次貼文策展 ❤️ 使用貼文功能並完成這次的指定任務,還有機會獲得富士即可拍,讓你的美好回憶都可以用即可拍珍藏!
Thumbnail
雖然神凰等人對於最後荒玥的一番兇殘手段實在是有看沒有懂,可他們這些遠道回娘家的先天生靈們沒有很懂,帝荒部眾們卻都很懂啊!
Thumbnail
妥協(Compromise)是關係中非常、非常困難的事情,當你有「妥協」,而且它與愛之匣門有關,那麼你真的有必須處理的事情。
Thumbnail
現階段中、美、歐世界三大地區,都面臨異常高溫而導致的能源問題! 首先是我們早在 8/3 粉專貼文中提到的,歐洲萊茵河只要下降到 40 厘米,那麼內河航運將可能無法使用,這將嚴重衝擊原油、天然氣、煤炭等原物料的運輸。 全球同時遭遇非常嚴重的旱災,導致用電需求大增,能源問題再度成為焦點!
Thumbnail
非常難看的一本書,優點只有封面漂亮,其他沒了。當初看博客來上一片好評,還有比爾蓋茲評論這本有愛情、懸疑、間諜、歷史等,所以就買了,結果覺得根本被騙,除了歷史之外,其他通通都沒有。 故事在講動盪的俄共時代,小人物的日常。四百多頁的內容,沒有任何劇情起伏,就真的全都是瑣碎的日常。
Thumbnail
魚油Omega3過量服用可能會影響凝血功能,增加出血風險。腹瀉、胃氣漲等也是食用魚油Omega3的常見副作用,尤其是服食高劑量、空腹服食魚油Omega3時容易引起胃灼熱、胃酸倒流、噁心、嘔吐、食慾減退等狀況。
Thumbnail
什麼!狗狗也可能罹患憂鬱症?雖然關於狗狗憂鬱症狀的研究還不夠完整,也無法像人類一樣分出輕重度;但根據不少主人的描述,狗狗的確會表現出憂鬱的情況!而這些症狀恰恰與跟人類憂鬱症很相似,當狗狗面對生活環境改變,或是主人的情緒不穩定,就容易讓狗狗沒有安全感,造成狗狗憂鬱症。一般常聽到狗狗的分離焦慮症、強迫
Thumbnail
高血壓、糖尿病、腎病患者。若再患上高血脂的話,須特別留意這些病患並發的嚴重危害。 肥胖人士。 代謝能力差的人士,如,男生大於45嵗、女生大於55嵗、或絕經的女士生。 有高血脂家族史的人士。 有早發性冠心病家族史的人士。
Thumbnail
充其量不過是想擁有別的女孩也擁有的東西。 校門前的名車、路人手上的名牌包、擦肩而過的美妙芳香; 一個普通的出生、快要擁有卻又失去的、複雜難懂的氣質; 「沒有男人愛的女人,和死了一樣。」和香奈兒一樣,從
Thumbnail
重點摘要: 1.9 月降息 2 碼、進一步暗示年內還有 50 bp 降息 2.SEP 上修失業率預期,但快速的降息速率將有助失業率觸頂 3.未來幾個月經濟數據將繼續轉弱,經濟復甦的時點或是 1Q25 季底附近
Thumbnail
近期的「貼文發佈流程 & 版型大更新」功能大家使用了嗎? 新版式整體視覺上「更加凸顯圖片」,為了搭配這次的更新,我們推出首次貼文策展 ❤️ 使用貼文功能並完成這次的指定任務,還有機會獲得富士即可拍,讓你的美好回憶都可以用即可拍珍藏!
Thumbnail
雖然神凰等人對於最後荒玥的一番兇殘手段實在是有看沒有懂,可他們這些遠道回娘家的先天生靈們沒有很懂,帝荒部眾們卻都很懂啊!
Thumbnail
妥協(Compromise)是關係中非常、非常困難的事情,當你有「妥協」,而且它與愛之匣門有關,那麼你真的有必須處理的事情。
Thumbnail
現階段中、美、歐世界三大地區,都面臨異常高溫而導致的能源問題! 首先是我們早在 8/3 粉專貼文中提到的,歐洲萊茵河只要下降到 40 厘米,那麼內河航運將可能無法使用,這將嚴重衝擊原油、天然氣、煤炭等原物料的運輸。 全球同時遭遇非常嚴重的旱災,導致用電需求大增,能源問題再度成為焦點!
Thumbnail
非常難看的一本書,優點只有封面漂亮,其他沒了。當初看博客來上一片好評,還有比爾蓋茲評論這本有愛情、懸疑、間諜、歷史等,所以就買了,結果覺得根本被騙,除了歷史之外,其他通通都沒有。 故事在講動盪的俄共時代,小人物的日常。四百多頁的內容,沒有任何劇情起伏,就真的全都是瑣碎的日常。
Thumbnail
魚油Omega3過量服用可能會影響凝血功能,增加出血風險。腹瀉、胃氣漲等也是食用魚油Omega3的常見副作用,尤其是服食高劑量、空腹服食魚油Omega3時容易引起胃灼熱、胃酸倒流、噁心、嘔吐、食慾減退等狀況。
Thumbnail
什麼!狗狗也可能罹患憂鬱症?雖然關於狗狗憂鬱症狀的研究還不夠完整,也無法像人類一樣分出輕重度;但根據不少主人的描述,狗狗的確會表現出憂鬱的情況!而這些症狀恰恰與跟人類憂鬱症很相似,當狗狗面對生活環境改變,或是主人的情緒不穩定,就容易讓狗狗沒有安全感,造成狗狗憂鬱症。一般常聽到狗狗的分離焦慮症、強迫
Thumbnail
高血壓、糖尿病、腎病患者。若再患上高血脂的話,須特別留意這些病患並發的嚴重危害。 肥胖人士。 代謝能力差的人士,如,男生大於45嵗、女生大於55嵗、或絕經的女士生。 有高血脂家族史的人士。 有早發性冠心病家族史的人士。
Thumbnail
充其量不過是想擁有別的女孩也擁有的東西。 校門前的名車、路人手上的名牌包、擦肩而過的美妙芳香; 一個普通的出生、快要擁有卻又失去的、複雜難懂的氣質; 「沒有男人愛的女人,和死了一樣。」和香奈兒一樣,從