約束和異常處理

⼀. 類的約束
  ⾸先, 你要清楚. 約束是對類的約束. 好比. 如今. 你是⼀個項⽬經理. 而後呢. 你給⼿下
的⼈分活. 張三, 你處理⼀下普通⽤戶登陸, 李四, 你處理⼀下會員登陸, 王五, 你處理⼀下管
理員登陸. 那這個時候呢. 他們就開始分別取寫他們的功能了. 可是呢. 你要知道, 程序員不⼀
定會有那麼好的默契. 頗有可能三個⼈會寫徹底三個不一樣的⽅法. 就好比這樣:java

class Normal: # 張三, 普通⼈登陸
 def login(self):
 pass
class Member: # 李四, 會員登陸
 def denglu(self):
 pass
 
class Admin: # 王五, 管理員登陸
 def login(self):
 pass

    而後呢, 他們把這樣的代碼交給你了. 你看了⼀眼. 張三和王五還算OK 這個李四寫的是
什麼⿁? denglu.......難受不. 可是好⽍能⽤. 還能湊合. 可是這時. 你這邊要使⽤了. 問題就來
了.python

# 項⽬經理寫的總⼊⼝
def login(obj):
 print("準備驗證碼.......")
 obj.login()
 print("進⼊主⻚.......")

    對於張三和王五的代碼. 沒有問題. 可是李四的. 你是否是調⽤不了. 那如何避免這樣的
問題呢? 咱們要約束程序的結構. 也就是說. 在分配任務以前就應該把功能定義好. 而後分別
交給底下的程序員來完成相應的功能.
    在python中有兩種辦法來解決這樣的問題. 程序員

  1. 提取⽗類. 而後在⽗類中定義好⽅法. 在這個⽅法中什麼都不⽤⼲. 就拋⼀個異
      常就能夠了. 這樣全部的⼦類都必須重寫這個⽅法. 不然. 訪問的時候就會報錯. 
  2. 使⽤元類來描述⽗類. 在元類中給出⼀個抽象⽅法. 這樣⼦類就不得不給出抽象
      ⽅法的具體實現. 也能夠起到約束的效果. 算法

    ⾸先, 咱們先看第⼀種解決⽅案: ⾸先, 提取⼀個⽗類. 在⽗類中給出⼀個⽅法. 而且在⽅
法中不給出任何代碼. 直接拋異常. 數據庫

class Base:
 def login(self):
 raise Exception("你沒有實現login⽅法()")
class Normal(Base):
 def login(self):
 pass
class Member(Base):
 def denglu(self):
 pass
class Admin(Base):
 def login(self):
 pass
# 項⽬經理寫的總⼊⼝
def login(obj):
 print("準備驗證碼.......")
 obj.login()
 print("進⼊主⻚.......")
n = Normal()
m = Member()
a = Admin()
login(n)
login(m) # 報錯. 
login(a)

    在執⾏到login(m)的時候程序會報錯. 緣由是, 此時訪問的login()是⽗類中的⽅法. 可是⽗
類中的⽅法會拋出⼀個異常. 因此報錯. 這樣程序員就不得不寫login⽅法了. 從⽽對⼦類進⾏
了相應的約束.
在本⽰例中. 要注意. 咱們拋出的是Exception異常. ⽽Exception是全部異常的根. 咱們⽆法通
過這個異常來判斷出程序是由於什麼報的錯. 因此. 最好是換⼀個比較專業的錯誤信息. 最好
是換成NotImplementError. 其含義是. "沒有實現的錯誤". 這樣程序員或者項⽬經理能夠⼀⽬
瞭然的知道是什麼錯了. 就比如. 你犯錯了. 我就告訴你犯錯了. 你也不知道哪⾥錯了. 這時我
告訴你, 你xxx錯了. 你改也好改不是?c#

 

    第⼆套⽅案: 寫抽象類和抽象⽅法. 這種⽅案相對來講比上⼀個⿇煩⼀些. 須要給⼤家先
引入⼀個抽象的概念. 什麼是抽象呢? 想⼀下. 動物的吃. 你怎麼描述? ⼀個動物到底應該怎
麼吃? 是否是描述不清楚. 這⾥動物的吃就是⼀個抽象的概念. 只是⼀個動做的概念. 沒有具
體實現. 這種就是抽象的動做. 換句話說. 咱們若是寫⼀個⽅法. 不知道⽅法的內部應該到底
寫什麼. 那這個⽅法其實就應該是⼀個抽象的⽅法. 若是⼀個類中包含抽象⽅法. 那麼這個類
⼀定是⼀個抽象類. 抽象類是不能有實例的. 好比. 你看看⼀些抽象派的畫做. 在現實中是不
存在的. 也就⽆法創建實例對象與之相對應. 因此抽象類⽆法建立對象. 建立對象的時候會報
錯.安全

    在python中編寫⼀個抽象類比較⿇煩. 須要引入abc模塊中的ABCMeta和
abstractmethod這兩個內容. 來咱們看⼀個例⼦.函數

from abc import ABCMeta, abstractmethod
# 類中包含了抽象⽅法. 那此時這個類就是個抽象類. 注意: 抽象類能夠有普通⽅法
class IGame(metaclass=ABCMeta):
 # ⼀個遊戲到底怎麼玩⼉? 你能形容? 流程能⼀樣麼?
 @abstractmethod
 def play(self):
 pass
 def turn_off(self):
 print("破B遊戲不玩了, 脫坑了")
class DNFGame(IGame):
 # ⼦類必須實現⽗類中的抽象⽅法. 不然⼦類也是抽象類
 def play(self):
 print("dnf的玩⼉法")
# g = IGame() # 抽象類不能建立對象
dg = DNFGame()
dg.play()

  經過代碼咱們能發現. 這⾥的IGame對DNFGame進⾏了約束. 換句話說. ⽗類對⼦類進
⾏了約束.

  下來. 繼續解決咱們⼀開始的問題. 學習

from abc import ABCMeta, abstractmethod
class Base(metaclass=ABCMeta):
 @abstractmethod
 def login(self):
 pass
class Normal(Base):
 def login(self):
 pass
class Member(Base):
 def denglu(self): # 這個就沒⽤了
 pass
 def login(self): # ⼦類對⽗類進⾏實現
 pass
class Admin(Base):
 def login(self):
 pass
# 項⽬經理寫的總⼊⼝
def login(obj):
 print("準備驗證碼.......")
 obj.login()
 print("進⼊主⻚.......")
n = Normal()
m = Member()
a = Admin()
login(n)
login(m)
login(a)

  總結: 約束. 其實就是⽗類對⼦類進⾏約束. ⼦類必需要寫xxx⽅法. 在python中約束的
⽅式和⽅法有兩種:測試

  1. 使⽤抽象類和抽象⽅法, 因爲該⽅案來源是java和c#. 因此使⽤頻率仍是不多的
  2. 使⽤⼈爲拋出異常的⽅案. 而且儘可能拋出的是NotImplementError. 這樣比較專
      業, ⽽且錯誤比較明確.(推薦)

⼆. 異常處理
  ⾸先, 咱們先說⼀下, 什麼是異常? 異常是程序在運⾏過程當中產⽣的錯誤. 就比如. 你在
回家路上忽然天塌了. 那這個就屬於⼀個異常. 總之就是不正常. 那若是程序出現了異常. 怎
麼處理呢? 在以前的學習中咱們已經寫過相似的代碼了.
  咱們先製造⼀個錯誤. 來看看異常⻓什麼樣.

def chu(a, b):
 return a/b
ret = chu(10, 0)
print(ret)
結果: 
Traceback (most recent call last):
 File "/Users/sylar/PycharmProjects/oldboy/⾯向對象/day05.py", line 100, in
<module>
 ret = chu(10, 0)
 File "/Users/sylar/PycharmProjects/oldboy/⾯向對象/day05.py", line 98, in
chu
 return a/b
ZeroDivisionError: division by zero

  什麼錯誤呢. 除法中除數不能是0. 那若是真的出了這個錯. 你把這⼀堆信息拋給客戶
麼? 確定不能. 那如何處理呢?

def chu(a, b):
 return a/b
try:
 ret = chu(10, 0)
 print(ret)
except Exception as e:
 print("除數不能是0")
結果:
除數不能是0

  那try...except是什麼意思呢? 嘗試着運⾏xxxxx代碼. 出現了錯誤. 就執⾏except後⾯的
代碼. 在這個過程當中. 當代碼出現錯誤的時候. 系統會產⽣⼀個異常對象. 而後這個異常會向
外拋. 被except攔截. 並把接收到的異常對象賦值給e. 那這⾥的e就是異常對象. 那這⾥的
Exception是什麼? Exception是全部異常的基類, 也就是異常的跟. 換句話說. 全部的錯誤都
是Exception的⼦類對象. 咱們看到的ZeroDivisionError 其實就是Exception的⼦類. 那這樣
寫好像有點⼉問題撒. Exception表示全部的錯誤. 太籠統了. 全部的錯誤都會被認爲是Exception.
當程序中出現多種錯誤的時候, 就很差分類了, 最好是出什麼異常就⽤什麼來處理. 這樣就更加合理了.


因此在try...execpt語句中. 還能夠寫更多的except

try:
 print("各類操做....")
except ZeroDivisionError as e:
 print("除數不能是0")
except FileNotFoundError as e:
 print("⽂件不存在")
except Exception as e:
 print("其餘錯誤")

  此時. 程序運⾏過程當中. 若是出現了ZeroDivisionError就會被第⼀個except捕獲. 若是出
現了FileNotFountError就會被第⼆個except捕獲. 若是都不是這兩個異常. 那就會被最後的
Exception捕獲. 總之最後的Exception就是咱們異常處理的最後⼀個守⻔員. 這時咱們最常
⽤的⼀套寫法. 接下來. 給出⼀個完整的異常處理寫法(語法):

try:
 '''操做'''
except Exception as e:
 '''異常的⽗類,能夠捕獲全部的異常'''
else:
 '''保護不拋出異常的代碼, 當try中⽆異常的時候執⾏'''
finally:
 '''最後老是要執⾏我'''

  解讀: 程序先執⾏操做, 而後若是出錯了會走except中的代碼. 若是不出錯, 執⾏else中
的代碼. 不論處不出錯. 最後都要執⾏finally中的語句. ⼀般咱們⽤try...except就夠⽤了. 頂多
加上finally. finally⼀般⽤來做爲收尾⼯做.

  上⾯是處理異常. 咱們在執⾏代碼的過程當中若是出現了⼀些條件上的不對等. 根本不符
合個人代碼邏輯. 好比. 參數. 我要求你傳遞⼀個數字. 你非得傳遞⼀個字符串. 那對不起. 我
沒辦法幫你處理. 那如何通知你呢? 兩個⽅案.

  ⽅案⼀. 直接返回便可. 我無論你還不⾏麼?
  ⽅案⼆. 拋出⼀個異常. 告訴你. 我很差惹. 乖乖的聽話.
第⼀種⽅案是咱們以前寫代碼常常⽤到的⽅案. 但這種⽅案並不夠好. ⽆法起到警⽰做⽤. 所
以. 之後的代碼中若是出現了相似的問題. 直接拋⼀個錯誤出去. 那怎麼拋呢? 咱們要⽤到
raise關鍵字

def add(a, b):
 '''
 給我傳遞兩個整數. 我幫你計算兩個數的和
 :param :param a:
 :param :param b:
 :return :return:
 '''
 if not type(a) == int and not type(b) == int:
 # 當程序運⾏到這句話的時候. 整個函數的調⽤會被中斷. 並向外拋出⼀個異常.
 raise Exception("不是整數, 朕不能幫你搞定這麼複雜的運算.")
 return a + b
# 若是調⽤⽅不處理異常. 那產⽣的錯誤將會繼續向外拋. 最後就拋給了⽤戶
# add("你好", "我叫賽利亞")
# 若是調⽤⽅處理了異常. 那麼錯誤就不會丟給⽤戶. 程序也能正常進⾏
try:
 add("胡辣湯", "滋滋冒油的⼤腰⼦")
except Exception as e:
 print("報錯了. ⾃⼰處理去吧")

     當程序運⾏到raise. 程序會被中斷. 並實例化後⾯的異常對象. 拋給調⽤⽅. 若是調⽤⽅
不處理. 則會把錯誤繼續向上拋出. 最終拋給⽤戶. 若是調⽤⽅處理了異常. 那程序能夠正常
的進⾏執⾏.

    說了這麼多. 異常也知道如何拋出和處理了. 可是咱們如今⽤的都是⼈家python給的異
常. 若是某⼀天. 你寫的代碼中出現了⼀個⽆法⽤現有的異常來解決問題. 那怎麼辦呢? 彆着
急. python能夠⾃定義異常.

     ⾃定義異常: 很是簡單. 只要你的類繼承了Exception類. 那你的類就是⼀個異常類. 就這
麼簡單. 好比. 你要寫⼀個男澡堂⼦程序. 那這時要是來個女的. 你怎麼辦? 是否是要拋出⼀個
性別異常啊? 好. 咱們來完成這個案例:

# 繼承Exception. 那這個類就是⼀個異常類
class GenderError(Exception):
 pass
class Person:
 def __init__(self, name, gender):
 self.name = name
 self.gender = gender
def nan_zao_tang_xi_zao(person):
 if person.gender != "":
 raise GenderError("性別不對. 這⾥是男澡堂⼦")
p1 = Person("alex", "")
p2 = Person("eggon", "")
# nan_zao_tang_xi_zao(p1)
# nan_zao_tang_xi_zao(p2) # 報錯. 會拋出⼀個異常: GenderError
# 處理異常
try:
 nan_zao_tang_xi_zao(p1)
 nan_zao_tang_xi_zao(p2)
except GenderError as e:
 print(e) # 性別不對, 這⾥是男澡堂⼦
except Exception as e:
 print("反正報錯了")

  ok搞定. 可是, 若是是真的報錯了. 咱們在調試的時候, 最好是能看到錯誤源⾃於哪⾥?
怎麼辦呢? 須要引入另⼀個模塊traceback. 這個模塊能夠獲取到咱們每一個⽅法的調⽤信息.
⼜被成爲堆棧信息. 這個信息對咱們拍錯是頗有幫助的.

import traceback
# 繼承Exception. 那這個類就是⼀個異常類
class GenderError(Exception):
 pass
class Person:
 def __init__(self, name, gender):
 self.name = name
 self.gender = gender
def nan_zao_tang_xi_zao(person):
 if person.gender != "":
 raise GenderError("性別不對. 這⾥是男澡堂⼦")
p1 = Person("alex", "")
p2 = Person("eggon", "")
# nan_zao_tang_xi_zao(p1)
# nan_zao_tang_xi_zao(p2) # 報錯. 會拋出⼀個異常: GenderError
# 處理異常
try:
 nan_zao_tang_xi_zao(p1)
 nan_zao_tang_xi_zao(p2)
except GenderError as e:
 val = traceback.format_exc() # 獲取到堆棧信息
 print(e) # 性別不對. 這⾥是男澡堂⼦
 print(val)
except Exception as e:
 print("反正報錯了")
結果: 
性別不對. 這⾥是男澡堂⼦
Traceback (most recent call last):
 File "/Users/sylar/PycharmProjects/oldboy/⾯向對象/day05.py", line 155, in
<module>
 nan_zao_tang_xi_zao(p2)
 File "/Users/sylar/PycharmProjects/oldboy/⾯向對象/day05.py", line 144, in
nan_zao_tang_xi_zao
 raise GenderError("性別不對. 這⾥是男澡堂⼦")
GenderError: 性別不對. 這⾥是男澡堂⼦

  搞定了. 這樣咱們就能收放⾃如了. 當測試代碼的時候把堆棧信息打印出來. 可是當到了
線上的⽣產環境的時候把這個堆棧去掉便可.

四. MD5加密
  想⼀個事情. 你在銀⾏取錢或者辦卡的時候. 咱們都要輸入密碼. 那這個密碼若是就按照
咱們輸入的那樣去存儲. 是否是很不安全啊. 若是某⼀個程序員進入到了銀⾏的數據庫. ⽽銀
⾏的數據庫⼜存的都是明⽂(不加密的密碼)密碼. 這時, 整個銀⾏的帳戶⾥的信息都是很是非
常不安全的. 那怎麼辦才安全呢? 給密碼加密. 而且是不可逆的加密算法. 這樣. 即便獲取到了
銀⾏的帳戶和密碼信息. 對於⿊客⽽⾔都⽆法進⾏破解. 那咱們的帳號就相對安全了不少. 那
怎麼加密呢? 最常⻅的就是⽤MD5算法.
  MD5是⼀種不可逆的加密算法. 它是可靠的. 而且安全的. 在python中咱們不須要⼿寫
這⼀套算法. 只須要引入⼀個叫hashlib的模塊就能搞定MD5的加密⼯做

import hashlib
obj = hashlib.md5()
obj.update("alex".encode("utf-8")) # 加密的必須是字節
miwen = obj.hexdigest()
print(miwen) # 534b44a19bf18d20b71ecc4eb77c572f

  那這樣的密⽂安全麼? 實際上是不安全的. 當咱們⽤這樣的密⽂去找⼀個所謂的MD5解密
⼯具. 是有可能解密成功的.

 

 

這就尷尬了. MD5不是不可逆麼? 注意. 這⾥有⼀個叫撞庫的問題. 就是. 因爲MD5的原
始算法已經存在好久了. 那就有⼀些⼈⽤⼀些簡單的排列組合來計算MD5. 而後當出現相同
的MD5密⽂的時候就很容易反推出原來的數據是什麼. 因此並非MD5可逆, ⽽是有些別有
⽤⼼的⼈把MD5的常⻅數據已經算完並保留起來了.

  那如何應對呢? 加鹽就⾏了. 在使⽤MD5的時候. 給函數的參數傳遞⼀個byte便可. 

 

import hashlib
obj = hashlib.md5(b"fjlksajflkjasfsalwer123dfskjf") # 加鹽
obj.update("alex".encode("utf-8")) # 加密的必須是字節
miwen = obj.hexdigest()
print(miwen) # 99fca4b872fa901aac30c3e952ca786d
 

此時你再去任何⽹站去試. 累死他也解不開密. 

那MD5如何應⽤呢? 

import hashlib
def my_md5(s):
 obj = hashlib.md5(b"fjlksajflkjasfsalwer123dfskjf")
 obj.update(s.encode("utf-8")) # 加密的必須是字節
 miwen = obj.hexdigest()
 return miwen
# alex: 99fca4b872fa901aac30c3e952ca786d
username = input("請輸⼊⽤戶名:")
password = input("請輸⼊密碼:")
# 數據存儲的時候.
# username: my_md5(password)
# 假設如今的⽤戶名和密碼分別是
# wusir: 99fca4b872fa901aac30c3e952ca786d ==> wusir: alex
# ⽤戶登陸
if username == "wusir" and my_md5(password) ==
"99fca4b872fa901aac30c3e952ca786d":
 print("成功")
else:
 print("失敗")
 

  因此. 之後存密碼就不要存明⽂了. 要存密⽂. 安全, 而且. 這⾥加的鹽不能改來改去的.
不然, 整套密碼就都亂了.

五. ⽇志
  ⾸先, 你要知道在編寫任何⼀款軟件的時候, 都會出現各類各樣的問題或者bug. 這些問
題或者bug⼀般都會在測試的時候給處理掉. 可是多多少少的都會出現⼀些意想不到的異常
或者錯誤. 那這個時候, 咱們是不知道哪⾥出了問題的. 由於不少BUG都不是必現的bug. 若是
是必現的. 測試的時候確定能測出來. 最頭疼的就是這種沒必要現的bug. 我這跑沒問題. 客戶那
⼀⽤就出問題. 那怎麼辦呢?咱們須要給軟件準備⼀套⽇志系統. 當出現任何錯誤的時候. 我
們均可以去⽇志系統⾥去查. 看哪⾥出了問題. 這樣在解決問題和bug的時候就多了⼀個幫⼿.
那如何在python中建立這個⽇志系統呢? 很簡單.

  1. 導入logging模塊.
  2. 簡單配置⼀下logging
  3. 出現異常的時候(except). 向⽇志⾥寫錯誤信息.

# filename: ⽂件名
# format: 數據的格式化輸出. 最終在⽇志⽂件中的樣⼦
# 時間-名稱-級別-模塊: 錯誤信息
# datefmt: 時間的格式
# level: 錯誤的級別權重, 當錯誤的級別權重⼤於等於leval的時候纔會寫⼊⽂件
logging.basicConfig(filename='x1.txt',
 format='%(asctime)s - %(name)s - %(levelname)s -%
(module)s: %(message)s',
 datefmt='%Y-%m-%d %H:%M:%S',
level=0) # 當前配置表示 10以上的分數會被寫⼊⽂件
# CRITICAL = 50
# FATAL = CRITICAL
# ERROR = 40
# WARNING = 30
# WARN = WARNING
# INFO = 20
# DEBUG = 10
# NOTSET = 0
logging.critical("我是critical") # 50分. 最貴的
logging.error("我是error") # 40分
logging.warning("我是警告") # 警告 30
logging.info("我是基本信息") # 20
logging.debug("我是調試") # 10
logging.log(2, "我是⾃定義") # ⾃定義. 看着給分

簡單作個測試, 應⽤⼀下

class JackError(Exception):
 pass
for i in range(10):
 try:
 if i % 3 == 0:
 raise FileNotFoundError("⽂件不在啊")
 elif i % 3 == 1:
 raise KeyError("鍵錯了")
 elif i % 3 == 2:
 raise JackError("傑克Exception")
 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('s1', level=logging.ERROR)
logger1.addHandler(file_handler)
logger1.error('我是A系統')
# 再建立⼀個操做⽇志的對象logger(依賴FileHandler)
file_handler2 = logging.FileHandler('l2.log', 'a', encoding='utf-8')
file_handler2.setFormatter(logging.Formatter(fmt="%(asctime)s - %(name)s -
%(levelname)s -%(module)s: %(message)s"))
logger2 = logging.Logger('s2', level=logging.ERROR)
logger2.addHandler(file_handler2)
logger2.error('我是B系統')
相關文章
相關標籤/搜索