02-python 基礎語法知識-04-異常與錯誤處理

02-python 基礎語法知識-04-異常與錯誤處理

總體 要講的大綱內容 如下

  • 循環- for while
  • 流程中斷 continue break
  • 邏輯操作 and ,or ,not
  • 函數
  • 常用內置函數 (todo)
  • python中的異常與錯誤處理

​ 今天我們開始學習一個比較關鍵的一個概念叫 異常。 你可能沒有接觸 過什麼 叫異常,它是所有 編程語言都比不可少的話題。 程序 有時候 並沒有 我們想象的那麼強大,有時候 也會出現 一些報錯,導致程序 停止了。 這些報錯 我們 就稱爲 程序拋出了一個異常。

什麼是異常

簡單理解 就是 我們說在代碼執行的時候 發生了錯誤,這個時候就產生了一個異常, 程序就被迫中斷了。

不知道 我們之前介紹的 一些基礎的數據類型,我會說 某某方法 如果怎麼樣,就報錯 IndexError 。 這些 報錯 就是異常,當異常發生的時候,如果我們採取任何 措施,程序將會停止運行。

舉幾個 常見的報錯的例子

>>> nums = ["one", "two", 'three', 'four', 'five']
>>> nums[10]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
IndexError: list index out of range

IndexError 這裏 就是一個異常,這個異常發生在 訪問了一個 不存在的位置,python 解釋器不知道如何做,所以就報錯了。

>>> 'frank'.index('name')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
ValueError: substring not found

ValueError 是一個異常, 這裏想在 在字符串中尋找 一個子串name 發現沒有找到,也報錯了。這裏就是拋出了一個異常 .

>>> 1/0
Traceback (most recent call last):
  File "<input>", line 1, in <module>
ZeroDivisionError: division by zero

ZeroDivisionError 這裏也報錯了, 這裏很明顯 1 是不能除以0 的,這個一般人都知道。 所以 你強行 給計算機算這個值,python解釋器 也沒有辦法算出來。 這個 也會拋出異常。 ZeroDivisionError

>>> 
... person = {
...     'weight': 60,
...     'name': 'frank',
...     'height': 165,
...     'hobby': 'swimming'
... }
>>> 
>>> person['aaa']
Traceback (most recent call last):
  File "<input>", line 1, in <module>
KeyError: 'aaa'

在字典中 強行訪問不存在的key , 這個時候 python 解釋器 也不知道如何處理 ,然後也報錯了。 這次報錯叫 KeyError .

上面的這些例子 都叫異常。而且 不同的報錯 有不同的異常。 也就是異常是可以分類的。就是 有不同類型的異常,通過 不同的異常,我們可以大概知道 程序發生了什麼問題。 方便 我們 更好的排查問題。

異常發生時會發生什麼?

當異常發生的 時候, 程序 就會中斷,之後 的代碼 將不會執行。

def operate():
    print("operate begin")

    nums = ["one", "two", 'three', 'four', 'five']
    print(nums[1])

    # 這裏要發生異常
    print(nums[10])

    print(nums[-1])  #1
    print("operate end") #2


operate()

運行這段代碼 看看 會發生什麼, 這個函數一開始先 打印 index=1 元素, 然後打印index=10的元素,顯然不存在, 之後打印 最後一個元素。 最後輸出 一句話 代表函數結束了。

來看下運行結果

operate begin
two
Traceback (most recent call last):
  File "C:/Users/changfx/PycharmProjects/python-study/myerror.py", line 20, in <module>
    operate()
  File "C:/Users/changfx/PycharmProjects/python-study/myerror.py", line 14, in operate
    print(nums[10])
IndexError: list index out of range

Process finished with exit code 1

從結果可以看出 開始 正常打印, 當發生 IndexError 的報錯後, 程序就退出了。並沒有執行 後面 的語句。

沒有打印最後的結束語,也沒有打印 最後一個元素。

總結一下: 當異常發生的時候,如果我們沒有對異常進行處理。程序 就中斷 ,然後 程序就會異常的退出了。

如何處理異常

try except finally else

可以通過這個 來 捕獲可能發生的異常。

try:
    pass
	# 可能發生異常的代碼段
except IndexError as e:
    pass

來修改一下 上面的函數

def operate():
    print("operate begin")

    nums = ["one", "two", 'three', 'four', 'five']
    print(nums[1])

    try:
        # 這裏要發生異常
        print(nums[10])
    except IndexError as e:
        print(e)

    print(nums[-1])
    print("operate end")


operate()

"""結果
operate begin
two
list index out of range
five
operate end

"""

再次運行 這個代碼,發現 已經可以正常運行了。 程序正常退出了,這個時候 當發生 異常的時候 我們可以嘗試捕獲這個異常, 只要 這個異常被 捕獲了, 那麼程序就可以正常運行了。

對於 異常捕獲 try 語句 還有其他的形式 。 簡單說一下

有時候我們並不知道 哪些代碼段會發生異常,也有可能不發生異常。 這個時候就要考慮 用 try 把可能發生的異常 進行捕獲。

比如下面的函數, 我想 訪問 下標爲10 的元素,但是這個array 是作爲一個函數的參數傳入進來,我並不知道 這個array 是否有 下標 爲10 的元素, 所以 我用 try 嘗試捕獲異常。這裏我使用 這種結構 .

try:
	pass
except IndexError as e:
	pass
else:
	pass
	

這裏 else 語句代表 如果沒有發生異常,會執行 else 語句的內容,如果發生了異常則不會執行else 語句的內容。

def print_nums(array: list):
    print('print_nums begin')
    try:
        print(array[10])
    except IndexError as e:
        print(f"e={e}")
    else:
        print('end')

在console演示一下:

>>> def print_nums(array: list):
...     print('print_nums begin')
...     try:
...         print(array[10])
...     except IndexError as e:
...         print(f"e={e}")
...     else:
...         print('end')
... 

>>> array = list(range(5))
>>> print_nums(array)  # 發生異常
print_nums begin
e=list index out of range

>>> array2 = list(range(11))
>>> print_nums(array2) #沒有發生異常 
print_nums begin
10
end

發生的異常的時候 else 的語句沒有被執行,如果沒有發生異常, else 的語句正常執行了。 這個就是else 語句的作用。 只有當 try 的代碼段 沒有發生異常的時候, else 的語句纔會被執行。

在介紹一種 異常捕獲的形式

finally 子句, 這裏 finally 的語句 代表 無論 try 捕獲 這段代碼 是否發生異常, finally 永遠被執行。 這裏一般會做一些 資源釋放, 數據庫關閉連接之類的操作。

def print_nums(array: list):
    print('print_nums begin')
    try:
        # 可能發生的異常的代碼段
        print(array[10])
    except IndexError as e:
        print(f"e={e}")
    else:
        print('else end')
    finally:
        print("print_nums end")

在 console 演示

>>> def print_nums(array: list):
...     print('print_nums begin')
...     try:
...         # 可能發生的異常的代碼段
...         print(array[10])
...     except IndexError as e:
...         print(f"e={e}")
...     else:
...         print('else end')
...     finally:
...         print("print_nums end")
...         
>>> 

>>> print_nums(array=list(range(11)))  # 不會發生異常
print_nums begin
10
else end
print_nums end


>>> print_nums(array=list(range(5)))  # 會發生異常
print_nums begin
e=list index out of range
print_nums end

可以看出 無論發生 或者不發生異常,都會 執行 finally 子句的代碼段。這個就是 finally 的作用。

如何自己主動拋出一個異常

可以使用 raise 語句 拋出一個異常. 這裏 可以用來檢測 一些用戶輸入,如果輸入了一些不合法的值, 我們就可以主動 拋出一個異常。給出用戶一些錯誤提示信息。

raise IndexError("xxxxxxx")

手動 拋出一個異常,比如 人的年齡 不可能超過 100,和小於0 。這裏 手動拋出一個 ValueError 的異常

>>> 
>>> 
... age = 150
... if age > 100 or age < 0:
...     raise ValueError("age >100 or age<0 ")
... 
Traceback (most recent call last):
  File "<input>", line 4, in <module>
ValueError: age >100 or age<0 

你注意到了嗎? 這裏我拋出的異常 是 ValueError , 而不是 KeyError ,IndexError 。 所以這裏要提醒一下,異常 是有分類的,每一種異常 都有特定的含義,你需要了解這些 特定含義,才進行拋出特定的異常。 因爲 異常信息 是用來幫助我們來排查程序出現的問題,所以 拋出異常信息 要儘量準確,這樣才能方便排查 問題。

異常的分類

在python3 中 內置很多的異常,並且 異常也是 可以通過繼承的。 繼承的概念 屬於面向對象的概念。 如果不是很理解,簡單理解 的子承父業 差不多,下面以及的異常 肯定含有上面異常的所有的東西。 之後 我會單獨 解釋面向對象的知識。

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

所有的異常 都是 由 BaseException 派生出來的。 然後 對應 4個平級的異常, 我們通常 情況 只是 從 Exception 中 繼承 來重寫 自己定義的異常。 這裏我們 經常 遇見的 也就10 來種左右。 作爲了解知道即可。

  • SystemExit
  • KeyboardInterrupt
  • GeneratorExit
  • Exception

自定義異常

我們 有時候 可能不需要系統系統的異常,或者滿足不了我們的需要。 我們通常的做法是 繼承 Exception 來實現自己的異常。

1.如何自定義自己的 異常對象

大部分情況 我們只要繼承 Exception 即可。 有一個關鍵字 class 還記得 我說的,異常實際上一個類。所以我們想要實現自己的異常,一般 會寫一個 class 然後繼承 Exception 即可。

class InvalidAge(Exception):
    pass


age = int(input("請輸入你的年齡: "))
if age > 100 or age < 0:
    raise InvalidAge("你的年齡應該是 [0-100] 之間 ")
>>> class InvalidAge(Exception):
...     pass
... 
... 
... age = int(input("請輸入你的年齡: "))
... if age > 100 or age < 0:
...     raise InvalidAge("你的年齡應該是 [0-100] 之間 ")
... 
請輸入你的年齡: >? 123
Traceback (most recent call last):
  File "<input>", line 7, in <module>
InvalidAge: 你的年齡應該是 [0-100] 之間 

異常 是什麼? 異常是不是一種例外情況呢?

爲了 更好的解釋異常,我舉兩個數字相除的例子。來看下面的兩個函數

def divide(number, divisor):
    try:
        print(f"{number} / {divisor} = {number / divisor}")
    except ZeroDivisionError:
        print("You can't divide by zero ")


        
def divide2(number, divisor):
    if divisor == 0:
        print("You can't divide by zero ")
        return

    print(f"{number} / {divisor} = {number / divisor}")

這兩個函數 功能 是相同的,計算兩個數 相除。 應該知道 除數不能 作爲分母。 第一種方式 通過捕獲這個異常 來提示用戶 不要把除數變成0 , 第二種方式 是直接判斷除數 是否爲0 。

第二種方法 好不好呢? 其實 我不能告訴你 這樣做好不好 。

但是 對於Python 程序員 更加細化追隨 請求諒解,而不是許可的原則。 就是說 python鼓勵程序員先去做,然後 在去解決問題。 顯然 python 是鼓勵 大家勇於嘗試的語言,實際上看 內置的庫,也是遵守這個原則的。

當然還有另一種 原則三思而後行的原則 這種做法也有一定好處, 第一 減少了不必要的cpu 的開銷,減少了一些不必要執行的代碼。 這裏對於例外情況 是非常有效的。

比如說 舉個例子,在一個超市銷售系統裏面,有一個貨物賣完了,顯然這個 時候 我們應該返回什麼呢?

空,缺貨的字符串,一個負數 ? 我們通過if 語句判斷 或取得數量 顯然 這個邏輯 寫起來 比較繁瑣。

爲啥 這個時候 我們不可以 自定義一個異常呢?

class OutOfStack(Exception):
    pass

只要在每次用戶調用購買的時候 ,我們try 進行捕獲這段購買的邏輯。然後在購買的時候 ,如果判斷缺貨了,直接拋出 這個 我們自定義的異常。 這樣的話,是不是看起來 更加好一點呢。

這裏你可能對 面向對象 不理解,之後我們 慢慢解釋一下 什麼是面向對象編程。

class OutOfStack(Exception):
    pass

class Goods:

    def __init__(self, stock: int):
        self.stock = stock

    def purchase(self):
        if self.stock == 0:
            raise OutOfStack("庫存爲0,無法購買了!")
        self.stock = self.stock - 1
        print(f"購買成功。 當前庫存容量:{self.stock}")

在控制檯 執行

>>> class OutOfStack(Exception):
...     pass
... 
... 
... class Goods:
... 
...     def __init__(self, stock: int):
...         self.stock = stock
... 
...     def purchase(self):
...         if self.stock == 0:
...             raise OutOfStack("庫存爲0,無法購買了!")
...         self.stock = self.stock - 1
...         print(f"購買成功。 當前庫存容量:{self.stock}")
... 
>>> 
>>> goods = Goods(3)
>>> goods.purchase()
購買成功。 當前庫存容量:2
>>> goods.purchase()
購買成功。 當前庫存容量:1
>>> goods.purchase()
購買成功。 當前庫存容量:0
>>> goods.purchase()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 12, in purchase
OutOfStack: 庫存爲0,無法購買了!

當前庫存爲0 ,然後程序 拋出一個異常。告訴用戶 庫存不夠了。

​ 用異常 來進行流程控制 可以完成一些 非常好用的程序設計。 還有要明白,發生異常的時候並不意味着 你要努力阻止 這個這種情況發生。 相反 ,它是一種 無法直接 交流的代碼 進行 信息溝通的方式而已。

如何安裝 第三方的包?

什麼是第三方包,就是一些別人覺得 非常有用的功能,寫成一個工具包,這樣 我們遇到 類似問題的時候,就不要重頭開始寫這些工具包,直接別人寫好的工具就可以了。 是不是很好啊,直接竊取別人的勞動果實。哈哈,其實 這就是一種開源精神。 每個有追求的程序員都應該有這份追求。

那麼問題來了 如何尋找 這些 工具包呢?

有一個網站 pypi.org 所有python的第三方包都會放到這個網站上面,可以自行搜索 查找。

下面 我們來安裝 一下 這幾個包

celery attrs requests redis

首先 你要知道 這些包的名稱, 然後安裝它 。

第一種方法 使用pip 工具安裝

pip install package_name 

打開git bash ,找到你的項目路徑

$ cd python-study/
(venv)
CHANGFX@xxxxxxx ~/PycharmProjects/python-study
$ pwd
/c/Users/changfx/PycharmProjects/python-study
(venv)
CHANGFX@xxxxxx ~/PycharmProjects/python-study

# 這裏是激活 虛擬環境
$ source venv/Scripts/activate
(venv)
CHANGFX@xxxxxx ~/PycharmProjects/python-study
$
(venv)
CHANGFX@xxxxxx  ~/PycharmProjects/python-study
$ pip install redis
$ pip install requests

第二種 是pycharm 自帶的安裝工具

導航欄 中 File > Settings

找到 Project 然後點擊 Project ,看到下面的畫面,點擊 下圖中的加號

img-02-04-1

直接 在搜索欄 搜索 redis , 就能夠看到 redis 相關的package ,點擊 install package 就可以下載了。

img-02-04-02

同理 可以下載 ,把下面四個包 下載一下, 注意不要下載錯了哦。

celery attrs requests redis

查看第三方包 自定義的異常

當你安裝 完成後,你可以查看 自己的venv 這個目錄下面 就會發現 已經有下面的路徑了。

我讓你安裝 這些包,主要讓你 看下,別人 如何定義自己的異常的。 然後感受一下。

img-02-04-03

當下載完成之後 要自己查看 一下 這些文件,可以看到都有自己定義一些異常類。

主要看下面的文件,都是 第三方包 自己定義的一些異常。

venv/Lib/site-packages/celery/exceptions.py

venv/Lib/site-packages/attr/exceptions.py

venv/Lib/site-packages/requests/exceptions.py

venv/Lib/site-packages/redis/exceptions.py

總結

​ 今天 主要講解了 python 中的異常,一些概念。 異常實際上是一個對象。 我們根據自己的需要 定義自己的異常,方便 我們 更好的定位問題,查找錯誤等。 異常 是一個非常好的工具,之後慢慢體會。 先要明白如何處理異常,如何定義異常等。 加油!

參考文檔

exceptions

Python3 錯誤和異常

python3 面向對象編程

分享快樂,留住感動. 2020-04-03 21:08:48 --frank
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章