天天看點

Python異常處理

一、什麼是異常

異常是程式發生錯誤的信号。程式一旦出現錯誤,便會産生一個異常,若程式中沒有處理它,就會抛出異常,程式的運作也随之終止。

在Python中,錯誤觸發的異常如下:

Python異常處理

而錯誤分成兩種,一種是文法上的錯誤SyntaxError,這種錯誤應該在程式運作前就修改正确:

>>> if  
File "<stdin>", line 1
   if
    ^
SyntaxError: invalid syntax
           

另一種就是邏輯錯誤,出現了之後盡快修改即可,常見的邏輯錯誤如:

# TypeError:數字類型無法與字元串類型相加
1 + ’2’  

# ValueError:當字元串包含有非數字的值時,無法轉成int類型
num = input(">>: ")  # 輸入hello
int(num)
 
# NameError:引用了一個不存在的名字x
x
 
# IndexError:索引超出清單的限制
lis = ['jason', 'tony']
lis[3]
 
# KeyError:引用了一個不存在的key
dic = {'name': 'jason'}
dic['age']
 
# AttributeError:引用的屬性不存在
class Foo:
    pass

Foo.x
 
# ZeroDivisionError:除數不能為0
1 / 0
           
Python異常處理

二、異常處理

為了保證程式的容錯性與可靠性,即在遇到錯誤時有相應的處理機制不會任由程式崩潰掉,我們需要對異常進行處理,處理的基本文法格式如下:

try:
    被檢測的代碼塊
except 異常類型:
    檢測到異常,就執行這個位置的邏輯
           

舉例如下:

try:
    print('start...')
    print(x)  # 引用了一個不存在的名字,觸發異常NameError
    print('end...')
except NameError as e:
    print('異常值為:%s' % e)
print('run other code...')

"""as文法将異常類型的值指派給變量e,這樣我們通過列印e便可以知道錯誤的原因"""
           

執行結果為:

start...
異常值為:name 'x' is not defined
run other code...
           

本來程式一旦出現異常就整體結束掉了,有了異常處理以後,在被檢測的代碼塊出現異常時,被檢測的代碼塊中異常發生位置之後的代碼将不會執行,取而代之的是執行比對異常的except子代碼塊,其餘代碼正常運作。

當被檢測的代碼塊中有可能觸發不同類型的異常時,針對不同類型的異常:

如果我們想分别用不同的邏輯處理,需要用到多分支的except(類似于多分支的elif,從上到下依次比對,比對成功一次便不再比對其他):

try:
    被檢測的代碼塊
except NameError:
    觸發NameError時對應的處理邏輯
except IndexError:
    觸發IndexError時對應的處理邏輯
except KeyError:
    觸發KeyError時對應的處理邏輯
           
def convert_int(obj):
    try:
        res = int(obj)
    except ValueError as e:
        print('ValueError: %s' % e)
        res = None
    except TypeError as e:
        print('TypeError: %s' % e)
        res = None
    return res

convert_int('jason')  # ValueError: invalid literal for int() with base 10: 'jason'
convert_int({'n': 1})  # TypeError: int() argument must be a string, a bytes-like object or a number, not 'dict'
           

如果我們想多種類型的異常統一用一種邏輯處理,可以将多個異常放到一個元組内,用一個except比對:

try:
    被檢測的代碼塊
except (NameError, IndexError, TypeError):
    觸發NameError或IndexError或TypeError時對應的處理邏輯
           
def convert_int(obj):
    try:
        res = int(obj)
    except (ValueError, TypeError):
        print('argument must be number or numeric string')
        res = None
    return res

convert_int('eason')  # argument must be number or numeric string
convert_int({'n': 1})  # argument must be number or numeric string
           

如果我們想捕獲所有異常并用一種邏輯處理,Python提供了一個萬能異常Exception:

try:
    被檢測的代碼塊
except NameError:
    觸發NameError時對應的處理邏輯
except IndexError:
    觸發IndexError時對應的處理邏輯
except Exception:
    其他類型的異常統一用此處的邏輯處理
           
Python異常處理

在多分支except之後還可以跟一個else(else必須跟在except之後,不能單獨存在),隻有在被檢測的代碼塊沒有觸發任何異常的情況下才會執行else的子代碼塊:

try:
    被檢測的代碼塊
except 異常類型1:
    pass
except 異常類型2:
    pass
......
else:
    沒有異常發生時執行的代碼塊
           

此外try還可以與finally連用,從文法上講finally必須放到else之後,但可以使用try-except-finally的形式,也可以直接使用try-finally的形式;

無論被檢測的代碼塊是否觸發異常,都會執行finally的子代碼塊,是以通常在finally的子代碼塊做一些回收資源的操作,比如關閉打開的檔案、關閉資料庫連接配接等。

try:
   被檢測的代碼塊
except 異常類型1:
   pass
except 異常類型2:
   pass
......
else:
   沒有異常發生時執行的代碼塊
finally:
   無論有無異常發生都會執行的代碼塊
           
f = None
try:
    f = open(‘db.txt’, 'r', encoding='utf-8')
    s = f.read().strip()
    int(s)  # 若字元串s中包含非數字時則會觸發異常ValueError
    f.close()  """若上面的代碼觸發異常,則根本不可能執行到此處的代碼,應該将關閉檔案的操作放到finally中"""
finally:
    if f:  # 檔案存在則f的值不為None
        f.close()
           

在不符合Python解釋器的文法或邏輯規則時,是由Python解釋器主動觸發的各種類型的異常,而對于違反程式員自定制的各類規則,則需要由程式員自己來明确地觸發異常,這就用到了raise語句,raise後必須是一個異常的類或者是異常的執行個體:

class Student:
    def __init__(self, name, age):
        if not isinstance(name, str):
            raise TypeError('name must be str')
        if not isinstance(age, int):
            raise TypeError('age must be int')

        self.name = name
        self.age = age

stu1 = Student(4573, 18)  # TypeError: name must be str
stu2 = Student('jason', '18')  # TypeError: age must be int
           

在内置異常不夠用的情況下,我們可以通過繼承内置的異常類來自定義異常類:

可以通過繼承Exception來定義一個全新的異常:

class PoolEmptyError(Exception):  
    def __init__(self, value = 'The proxy source is exhausted'):  # 可以定制初始化方法
        super(PoolEmptyError, self).__init__()
        self.value = value

    def __str__(self):  # 可以定義該方法用來定制觸發異常時列印異常值的格式
        return '< %s >' % self.value


class NetworkIOError(IOError):  # 也可以在特定異常的基礎上擴充一個相關的異常
    pass


raise PoolEmptyError  # __main__.PoolEmptyError: < The proxy source is exhausted >
raise NetworkIOError('連接配接被拒絕')  # __main__.NetworkIOError: 連接配接被拒絕
           

最後,Python還提供了一個斷言語句assert expression,斷定表達式expression成立,否則觸發異常AssertionError,與raise-if-not的語義相同,如下:

age = '18'

# 若表達式isinstance(age, int)傳回值為False則觸發異常AssertionError
assert isinstance(age, int)

# 等同于
if not isinstance(age, int):
	raise AssertionError
           
Python異常處理

三、何時使用異常處理

在了解了異常處理機制後,本着提高程式容錯性和可靠性的目的,讀者可能會錯誤地認為應該盡可能多地為程式加上try...except...,這其實是在過度消費程式的可讀性,因為try...except本來就是你附加給程式的一種額外的邏輯,與你的主要工作是沒有多大關系的。

如果錯誤發生的條件是“可預知的”,我們應該用if來進行”預防”,如下:

age = input('input your age>>: ').strip()
if age.isdigit():  # 可預知隻有滿足字元串age是數字的條件,int(age)才不會觸發異常,
    age = int(age)
else:
    print('You must enter the number')
           

如果錯誤發生的條件“不可預知”,即異常一定會觸發,那麼我們才應該使用try...except語句來處理。

例如我們編寫一個下載下傳網頁内容的功能,網絡發生延遲之類的異常是很正常的事,而我們根本無法預知在滿足什麼條件的情況下才會出現延遲,因而隻能用異常處理機制了:

import requests
from requests.exceptions import ConnectTimeout

def get(url):
    try:
        response = requests.get(url, timeout=3)  # 超過3秒未下載下傳成功則觸發ConnectTimeout異常
        res = response.text
    except ConnectTimeout:
        print('連接配接請求逾時')
        res = None
    except Exception:
        print('網絡出現其他異常')
        res = None
    return res

get('https://www.python.org')
           

總結:

  • 有可能會出現錯誤的代碼才需要被監測;
  • 被監測的代碼一定要越少越好;
  • 異常捕獲使用頻率越低越好;
Python異常處理

繼續閱讀