1. 程式人生 > >python D19 約束、日誌

python D19 約束、日誌

# 今日主要內容:
# 1. 約束(1. 丟擲異常, 2. 寫抽象類)(難點, 重點)
# 1. 丟擲異常; NotImplementedError
# 2. 抽象類
# from abc import ABCMeta, abstractmethod
#
# class Base(metaclass = ABCMeta): 抽象類
# @abstractmethod
# def 方法(self):pass
#
# class Foo(Base): 子類必須重寫父類中的抽象方法
# def 方法(self):
# pass
# 一個類包含類抽象方法. 這個類一定是抽象類
# 抽象類中可以有正常的方法
#
# 抽象類中如果有抽象方法. 這個類將不能建立物件
#
# 介面: 類中都是抽象方法
#
# 2. 異常處理
# try:
#
# except Error as e:
#
# except....
#
# else:
#
# finally:
# 收尾
#
# import traceback
# try:
# # 嘗試執行的程式碼
# except Exception as e:
# # 除了錯之後要做什麼
# traceback.format_exc() # 獲取堆疊資訊(錯誤資訊)
#
# 3. 日誌處理
# logging
#
# 錯誤等級
# critical
# error(最多)
# wraning
# info
# debug

# 一、類的約束
# ⾸先, 你要清楚. 約束是對類的約束. 比如. 現在. 你是⼀個項⽬經理. 然後呢. 你給⼿下
# 的⼈分活. 張三, 你處理⼀下普通⽤戶登入, 李四, 你處理⼀下會員登入, 王五, 你處理⼀下管
# 理員登入. 那這個時候呢. 他們就開始分別取寫他們的功能了. 但是呢. 你要知道, 程式設計師不⼀
# 定會有那麼好的默契. 很有可能三個⼈會寫完全三個不同的⽅法. 就比如這樣:
# class Normal: # 張三, 普通⼈登入
# def login(self):
# pass
# class Member: # 李四, 會員登入
# def denglu(self):
# pass
# class Admin: # 王五, 管理員登入
# def login(self):
# pass

# 然後呢, 他們把這樣的程式碼交給你了. 你看了⼀眼. 張三和王五還算OK 這個李四寫的是
# 什麼⿁? denglu.......難受不. 但是好⽍能⽤. 還能湊合. 但是這時. 你這邊要使⽤了. 問題就來了.
# 項⽬經理寫的總⼊⼝

# 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錯了. 你改也好改不是?

# 第⼆套⽅案: 寫抽象類和抽象⽅法. 這種⽅案相對來說比上⼀個⿇煩⼀些. 需要給⼤家先
# 引入⼀個抽象的概念. 什麼是抽象呢? 想⼀下. 動物的吃. 你怎麼描述? ⼀個動物到底應該怎
# 麼吃? 是不是描述不清楚. 這⾥動物的吃就是⼀個抽象的概念. 只是⼀個動作的概念. 沒有具
# 體實現. 這種就是抽象的動作. 換句話說. 我們如果寫⼀個⽅法. 不知道⽅法的內部應該到底
# 寫什麼. 那這個⽅法其實就應該是⼀個抽象的⽅法. 如果⼀個類中包含抽象⽅法. 那麼這個類
# ⼀定是⼀個抽象類. 抽象類是不能有例項的. 比如. 你看看⼀些抽象派的畫作. 在現實中是不
# 存在的. 也就⽆法建立例項物件與之相對應. 所以抽象類⽆法建立物件. 建立物件的時候會報
# 錯.
# 在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() # 直接去找父類的login 由於longin是抽象方法不能實現,則會報錯,父類靜態方法對子類進行約束
# 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 # 應用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("性別不對. 這⾥是男澡堂⼦") # 性別不對. 這⾥是男澡堂⼦
# 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: 性別不對. 這⾥是男澡堂⼦
# 搞定了. 這樣我們就能收放⾃如了. 當測試程式碼的時候把堆疊資訊打印出來. 但是當到了
# 線上的⽣產環境的時候把這個堆疊去掉即可.

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

# 那如何在python中建立這個⽇志系統呢? 很簡單.
# 1. 匯入logging模組.
# 2. 簡單配置⼀下logging
# 3. 出現異常的時候(except). 向⽇志⾥寫錯誤資訊

import logging #匯入logging模板
# 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, "我是⾃定義") # ⾃定義. 看著給分

# 日誌格式都是一樣的格式
# 一、單日誌個事
# 1. 匯入logging模組.
# import logging
# 2. 簡單配置⼀下logging
# 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) # 可變部分 level=0 可以調整等級,等級多少以上會被寫入日誌
# 3. 出現異常的時候(except). 向⽇志⾥寫錯誤資訊
# logging.error("錯誤資訊")

# 簡單做個測試, 應⽤⼀下
# import logging
# import traceback
# logging.basicConfig(filename='日誌.txt', format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
# datefmt='%Y-%m-%d %H:%M:%S',
# level=0)
# class JackError(Exception): #自定異常
# pass
# for i in range(10):
# try:
# if i % 3 == 0:
# raise FileExistsError("檔案找不到") # 丟擲異常
# 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') # 'l1.log' 檔名可變
# file_handler.setFormatter(logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s"))
# logger1 = logging.Logger('s1', level=logging.ERROR) # "s1" 日誌系統名 可變
# logger1.addHandler(file_handler)
# logger1.error('s1')
#
# # 再建立⼀個操作⽇志的物件logger(依賴FileHandler)
# file_handler2 = logging.FileHandler('l2.log', 'a', encoding='utf-8') # 'l2.log' 檔名可變
# 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('s2') # "s1" 日誌系統名 可變