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

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

avatar-img
9會員
62內容數
从基础开始,再到Python,然后是CV、BI、NLP等相关技术。从头到尾详细的教授一边人工智能。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
茶桁的沙龍 的其他內容
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. 用户输入用户名和密码以及
你可能也想看
Google News 追蹤
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
本章節旨在介紹Java程式語言中的「例外處理」概念。透過各個小節,讀者將學習到何謂例外處理、為何要使用它、如何在Java中實現例外處理,以及如何正確地捕獲和處理各種類型的異常。此外,本章節還提供了如何主動觸發異常,以及如何創建和使用自定義異常的實例。
Thumbnail
這篇文章主要講解Kotlin的例外處理。內容包括例外處理的目的、`try-catch` 和 `finally` 的用法、常見的異常類型,以及如何定義和觸發自定義的異常訊息。
Thumbnail
本章節為Swift程式語言的異常處理介紹,說明了為何需要進行異常處理以及如何進行異常處理。提供了使用do、try、catch和throw關鍵字進行異常處理的基本語法並展示了其在實際程式中的應用。同時也說明了Swift中的一些常見異常類型,並且教導了如何主動觸發異常訊息和定義自己的異常類型。
Thumbnail
當你在開發程式時,難免會遇到各種錯誤和異常情況。這些錯誤可能是因為代碼中的錯誤、外部資源無法訪問或其他不可預期的狀況。為了提高程式的可靠性、穩定性和可維護性,我們使用「例外處理」來處理這些異常情況。
Thumbnail
本章節介紹C#的「例外處理」,包括使用try-catch語法處理錯誤,finally關鍵字的使用,以及如何主動引發和自定義異常。
Thumbnail
例外處理是Python中的重要概念,用於控制並處理程序異常,防止程序崩潰和數據損失。它包括try, except, else和finally等語法結構,可用於對特定錯誤進行處理,或主動觸發和自定義異常。
Thumbnail
在實務上,若Python報錯時,若引入的套件越多伴隨的異常訊息會變得越來越複雜,看到一推密密麻麻的內容時,很多時候都想直接跳過。 本文將利用Traceback來讓異常訊息變得更好理解。
Thumbnail
在程式中,了解資料型態是相當重要的。 為什麽? 因為許多error,常常都是因為資料型態不正確所導致的。 舉個例子,在python中: a = 1 + 2 print(a) 結果就是3 a = = "1"+"2" print(a) 結果就是12 是不是差很多? 所以今天我來介
Thumbnail
在現實生活中,充滿的警報及安全措施,總會設個安全線在那,若觸碰到底線時則會有警報響起。 在Python也有類似的作法,如果希望在某個條件達到時,就拉起警報不要讓程式繼續進行下去,就適合使用raise 這種機制讓開發者能夠在程序執行時檢測到不正確的條件,然後通過引發異常停止程序的執行或通知使用者。
Thumbnail
本文介紹Python程式設計中處理異常的try, except, else, finally語句,並提供程式範例來更深刻理解使用方法。
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
本章節旨在介紹Java程式語言中的「例外處理」概念。透過各個小節,讀者將學習到何謂例外處理、為何要使用它、如何在Java中實現例外處理,以及如何正確地捕獲和處理各種類型的異常。此外,本章節還提供了如何主動觸發異常,以及如何創建和使用自定義異常的實例。
Thumbnail
這篇文章主要講解Kotlin的例外處理。內容包括例外處理的目的、`try-catch` 和 `finally` 的用法、常見的異常類型,以及如何定義和觸發自定義的異常訊息。
Thumbnail
本章節為Swift程式語言的異常處理介紹,說明了為何需要進行異常處理以及如何進行異常處理。提供了使用do、try、catch和throw關鍵字進行異常處理的基本語法並展示了其在實際程式中的應用。同時也說明了Swift中的一些常見異常類型,並且教導了如何主動觸發異常訊息和定義自己的異常類型。
Thumbnail
當你在開發程式時,難免會遇到各種錯誤和異常情況。這些錯誤可能是因為代碼中的錯誤、外部資源無法訪問或其他不可預期的狀況。為了提高程式的可靠性、穩定性和可維護性,我們使用「例外處理」來處理這些異常情況。
Thumbnail
本章節介紹C#的「例外處理」,包括使用try-catch語法處理錯誤,finally關鍵字的使用,以及如何主動引發和自定義異常。
Thumbnail
例外處理是Python中的重要概念,用於控制並處理程序異常,防止程序崩潰和數據損失。它包括try, except, else和finally等語法結構,可用於對特定錯誤進行處理,或主動觸發和自定義異常。
Thumbnail
在實務上,若Python報錯時,若引入的套件越多伴隨的異常訊息會變得越來越複雜,看到一推密密麻麻的內容時,很多時候都想直接跳過。 本文將利用Traceback來讓異常訊息變得更好理解。
Thumbnail
在程式中,了解資料型態是相當重要的。 為什麽? 因為許多error,常常都是因為資料型態不正確所導致的。 舉個例子,在python中: a = 1 + 2 print(a) 結果就是3 a = = "1"+"2" print(a) 結果就是12 是不是差很多? 所以今天我來介
Thumbnail
在現實生活中,充滿的警報及安全措施,總會設個安全線在那,若觸碰到底線時則會有警報響起。 在Python也有類似的作法,如果希望在某個條件達到時,就拉起警報不要讓程式繼續進行下去,就適合使用raise 這種機制讓開發者能夠在程序執行時檢測到不正確的條件,然後通過引發異常停止程序的執行或通知使用者。
Thumbnail
本文介紹Python程式設計中處理異常的try, except, else, finally語句,並提供程式範例來更深刻理解使用方法。