本節主要內容:
1.類的約束
2.異常處理
3.自定義異常
4.日誌
一.類的約束
⾸先, 你要清楚. 約束是對類的約束. 比如. 現在. 你是一個項⽬經理. 然後呢. 你給手下 的人分活. 張三, 你處理一下普通使用者登入,
李四, 你處理一下會員登入, 王五, 你處理一下管理員登入. 那這個時候呢. 他們就開始分別取寫他們的功能了了. 但是呢. 你要知道,
程式設計師不一定會有那麼好的默契. 很有可能三個人會寫完全三個不同的方法. 就比如這樣:
# 貼吧 # 專案經理(級別高一點兒) class Base: def login(self): # 強制子類做xxxx事 raise NotImplementedError("子類沒有實現該方法") # 報錯. 拋異常 # 1. 普通賬號 --> 張三 class Normal(Base): def login(self): print("普通賬號的登入") # 2. 吧務 - > 李四 class Member(Base): def login(self): print("吧務的登入") # 3. 百度員工 -> 王五 class Admin(Base): def login(self): # 方法的覆蓋和重寫 print("管理員的登入") # 專案經理 def wodetian(obj): obj.login() n = Normal() wodetian(n) m = Member() wodetian(m) a = Admin() wodetian(a)
然後呢, 他們把這樣的程式碼交給你了. 你看一眼. 張三和王五還算OK 這個李四寫的是 什麼鬼? denglu…….難受不.
但是好歹能用. 還能湊合. 但是這時. 你這邊要使用了. 問題就來了.
對於張三和王五的程式碼. 沒有問題. 但是李四的. 你是不是呼叫不了. 那如何避免這樣的 問題呢? 我們要約束程式的
結構. 也就是說. 在分配任務之前就應該把功能定義好. 然後分別交給底下的程式設計師來完成相應的功能.
約束的作用:規範程式碼,約束是對類的約束
在python中有兩種辦法解決這樣的問題:
1.提取父類,然後在父類中定義好辦法.在這個方法中什麼都不用幹,就丟擲一個異常就可以了,這樣所有的子類就必須重寫這個方法.
否則,訪問的時候就會報錯.
2.使用元類來描述父類.在元類中給出一個抽象方法.這樣子類就不得不給出抽象方法的具體實現.也可以起到約束的效果.
首先,我們先看第一張解決方案:首先,提取一個父類,在父類中給出一個方法,並且在方法中不給出任何程式碼,直接丟擲異常.
class Base: def login(self): raise NotImplementedError class Normal(Base): def login(self): print("普通使用者登入") class Member(Base): def login(self): print("會員登入") class Admin(Base): def denglu(self): print("管理員登入") #專案經理寫的總入口 def login(obj): print("準備驗證碼...") obj.login() print("進入主頁...") n = Normal() m = Member() a = Admin() login(n) login(m) login(a) #報錯
在執行到login(a)的時候程式會報錯. 原因是, 此時訪問的login()是父類中的方法. 但是父類中的方法會丟擲一個異常. 所以報錯.
這樣程式設計師就不得不寫login方法了. 從而對子類進行了相應的約束.
在本示例中. 要注意. 我們丟擲的是Exception異常. 而Exception是所有異常的根. 我們無法通過這個異常來判斷出程式是因為什麼
報的錯. 所以. 最好是換一個比較專業的錯誤資訊. 最好是換成NotImplementError. 其含義是. “沒有實現的錯誤”. 這樣程式設計師或者項
⽬經理理可以一目了然的知道是什麼錯了. 就好比. 你犯錯了. 我就告訴你犯錯了. 你也不知道哪里錯了. 這時我告訴你, 你xxx錯了.
你改也好改不是?
第二套方案: 寫抽象類和抽象方法. 這種方案相對來說比上一個麻煩一些. 需要給大家先引入一個抽象的概念我們如果寫了一個方法,
不知道方法的內部應該到底寫什麼.那這個方法其實就應該是一個抽象的方法.如果一個類中包含抽象方法,那麼這個類一定是抽象類
抽象類是不能有例項物件的.建立物件的時候會報錯.
在python中編寫一個抽象類需要引入abc模組中的ABCMeta和abstractmethod這兩個內容.
from abc import ABCMeta,abstractmethod # 類中包含了抽象方法,那此時這個類就是個抽象類.注意:抽象類可以有普通方法 class Animal(metaclass=ABCMeta): @abstractmethod def chi(self): pass # 抽象類不能建立物件 class Dog(Animal): # 子類必須實現父類中的抽象方法,否則子類也是抽象類 def chi(self): print("躺著吃") class Cat(Animal): def he(self): print("喝水") d = Dog() d.chi() c = Cat() c.he()
通過程式碼我們能發現,這裡的父類Animal對子類Dog和Cat進行了約束
總結:約束.其實就是父類對子類進行約束.子類必須要寫xxx方法.在python約束的方式有兩種:
1.使用抽象類和抽象方法,由於該方案來源是Java和c#.所以使用評率還是很少的
2.使用人為丟擲異常的方案,並且儘量丟擲的是NotImplementError.這樣比較專業,而且錯誤比較明確.(推薦)
二.異常處理
異常:程式在執行過程中產生的錯誤.
def cul(a,b): return a/b ret = cul(10/0) print(ret) 結果: Traceback (most recent call last): File "D:/python課件及作業/約束/121.py", line 4, in <module> ret = cul(10/0) ZeroDivisionError: division by zero
什麼是錯誤,除法中除數不能是0.那如果真的出了這個錯.我們不可能吧一堆錯誤資訊拋給客戶,那該如何處理?
def cul(a,b): return a/b try: ret = cul(10/0) print(ret) except Exception as e: print("除數不能是0")
try..except的作用就是當程式執行時出現了錯誤,就執行except後面的程式碼.在和這個過程中.當程式碼出現錯誤的時候,
系統會產生⼀個異常物件. 然後這個異常會向外拋. 被except攔截. 並把接收到的異常物件賦值給e. 那這里的e就是
異常物件. 那這里的 Exception是什麼? Exception是所有異常的基類, 也就是異常的根. 換句話說. 所有的錯誤都是
Exception的子類物件. 我們看到的ZeroDivisionError 其實就是Exception的子類. 那這樣 寫好像有點兒問題. Exception
表示所有的錯誤. 太籠統了了. 所有的錯誤都會被認為是Exception. 當程式中出現多種錯誤的時候, 就不好分類了了, 最
好是出什麼異常就⽤用什麼來處理. 這樣就更加合理了. 所以在try…execpt語句中. 還可以寫更多的except.
完整的異常處理寫法(語法):
try:
"""操作"""
except Exception as e:
"""異常的父類,可以捕獲異常"""
else:
"""保護不丟擲異常的程式碼,當try中無異常的時候執行"""
finally:
"""最後要執行的"""
解讀:程式先執行操作,然後如果出錯了會走except中的程式碼.如果不出錯,執行else中的程式碼.不論出不出錯,最後都要
執行finally中的語句,一般我們用try…except就夠用了.頂多加上finally.finally一般用來作為收尾工作.
以上是處理異常,我們在執行程式碼的過程中如果出現了一些條件上的不對等.根本不符合我的程式碼邏輯.比如,引數.我要求
傳遞的是一個數字,而客戶非得傳遞一個字串.那我們該如何處理來通知客戶呢?
方案一:直接返回即可.
方案二:丟擲一個異常.
那如何丟擲異常呢?我們要用到關鍵字raise
def add(a,b): """ 傳遞兩個整數,求和 :param a: :param b: :return: """ if not type(a) == int or not type(b) == int: #當程式執行到這句話的時候,正割函式的呼叫就會被中斷,並向外丟擲一個異常 raise Exception("不是整數,無法求和") return a + b # 如果呼叫方不處理異常,那產生的錯誤將會繼續向外拋,最後就拋給了使用者 # 如果呼叫方處理了異常. 那麼錯誤就不會丟給使用者. 程式也能正常執行 try: add("胡辣湯",1) except Exception as e:
當程式運行到raise. 程式會被中斷. 並例項化後面的異常物件. 拋給呼叫方. 如果呼叫方不處理. 則會把錯誤繼續向上丟擲. 最終拋給⽤使用者.
如果呼叫方處理了異常. 那程式可以正常的進行執行.
三.自定義異常
自定義異常:非常簡單,只要你的類繼承了Exception類,那你的類就是一個異常類.
import traceback class GenderError(Exception): pass class Person: def __init__(self,name,gender): self.name = name self.gender = gender def goto_WC(self): if self.gender == "男": print("進來吧") else: raise GenderError("錯了,不可以進來") try: p1 = Person("Andy","男") p1.goto_WC() p2 = Person("Amy","女") p2.goto_WC() except GenderError as e: val = traceback.format_exc() print("你不是男的,別來啊") print(val) except Exception as e: print("其他錯誤)
結果:
進來吧
你不是男的,別來啊
Traceback (most recent call last):
File “D:/python課件及作業/約束/約束.py”, line 83, in <module>
p2.goto_WC()
File “D:/python課件及作業/約束/約束.py”, line 77, in goto_WC
raise GenderError(“錯了,不可以進來”)
GenderError: 錯了,不可以進來
我們在除錯的時候最好是能看到錯誤院子哪裡,那怎麼辦?
上面的程式碼引入了另一個模組traceback,這個模組可以獲取到我們每個方法的呼叫資訊.又被稱為堆疊資訊,
這個資訊對我們拍錯是很有幫助的
四.日誌
在編寫任何一款軟體的時候,都會出現各種各樣的問題或者bug,這些問題或者bug一般都會在測試的時候處理掉.
但是多多少少的都會出現一些意想不到的異常或者錯誤.那這個時候,我們是不知道哪裡出現了問題.因為很多都
不是必現的bug.如果是必現的,測試的時候肯定能測出來.最頭疼的就是這種不必現的bug.自己執行沒有問題,但
是到客戶那一用就出問題.那怎麼辦?我們需要給軟體準備一套日誌系統.當出現任何錯誤的時候.我們都可以去日
志系統裡去檢視.看哪裡出了問題.這樣解決問題和bug的時候就多了一個幫手.那如何在python中建立這個日誌系
統呢?很簡單:
1.匯入logging模組.
2.簡單配置一下logging
3.出現異常的時候(except).向日志裡寫錯誤資訊.
import logging #filename:檔名 # format:資料的格式化輸出.最終在日誌檔案中的樣子 # 時間-名稱-級別-模組: 錯誤資訊 # datefmt:時間的格式 # level:錯誤的級別權重,當錯誤的級別權重大於等於leval的時候才會寫入檔案 logging.basicConfig(filename="x1.log",format=`%(asctime)s - %(name)s - %(levelname)s -%(module)s:` `%(message)s`,datefmt=`%Y-%m-%d %H:%M:%S`, level=10) #當前配置表示10以上的分數會被寫入日誌檔案 # critical = 50 # fatal = critical # error = 40 # warning = 30 # warn = warning # info = 20 # debug = 10 # notest = 0 logging.critical("我是critical") # 50分.最貴的 logging.error("我是error") # 40分 logging.warning("我是warning") # 警告 30分 logging.info("我是基本資訊") # 20 logging.debug("我是測試") # 10 logging.log(2,"我是自定義") # 自定義,看著給分
在上面這個模板的基礎上做個簡單的測試,應用下
import traceback class JackError(Exception): pass for i in range(10): try: if i % 3 == 0: raise FileNotFoundError("檔案不在啊") if i % 3 == 1: raise KeyError("鍵錯了") if i % 3 == 2: raise JackError except FileNotFoundError: val = traceback.format_exc() logging.error(val) except KeyError: val = traceback.format_exc() logging.error(val) except JackError: val = traceback.format_exc() logging.error(val) except Exception: val = traceback.format_exc() logging.error(val)
最後,如果你係統中想要把日誌檔案分開.比如,一個大專案,有兩個子系統,那兩個子系統要分開記錄日誌,方便除錯.
那怎麼辦呢?注意:用上面的basicConfig是搞不定的,我們要藉助檔案助手(FileHandler),來幫我們完成日誌的分開記錄:
import logging # 建立一個操作日誌的物件logger(依賴FileHandler) file_Handler = logging.FileHandler("l1.log", "a", encoding="utf-8") file_Handler.setFormatter(logging.Formatter(fmt="%(asctime)s - %(name)s - % (levelname)s -%(module)s:%(message)s")) logger1 = logging.Logger("日誌1",level=logging.ERROR) logger1.addHandler(file_Handler) logger1.error("我是A系統") # 再建立一個操作日誌的物件logger(依賴FileHandler) file_Handler = logging.FileHandler("l2.log", "a", encoding="utf-8") file_Handler.setFormatter(logging.Formatter(fmt="%(asctime)s - %(name)s - % (levelname)s -%(module)s:%(message)s")) logger2 = logging.Logger("日誌2",level=logging.ERROR) logger2.addHandler(file_Handler)