第044講:魔法方法:簡單定製
目錄
0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!
2. 既然咱都做到了這一步,那不如再深入一下。再次改進我們的程式碼,讓它能夠統計一個函式執行若干次的時間。
0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!
這節課我們一起來完成一個類的定製,
基本要求:
- 定製一個計時器的類
- start和stop方法代表啟動計時和停止計時
- 假設計時器物件 t1,print(t1) 和直接呼叫 t1 均顯示結果
- 當計時器未啟動或已經停止計時,呼叫stop方法會給予溫馨的提示
- 兩個計時器物件可以進行相加:t1 + t2
- 只能使用提供的有限資源完成
顯然,你需要這些資源:
- 使用time模組的localtime方法獲取時間
Python擴充套件閱讀:time 模組詳解(時間獲取和轉換)
- time.localtime 返回 struct_time 的時間格式
你可以用索引值直接去索引它。
- 因為直接呼叫物件就要顯示結果,所以需要表現你的類:__str__ 和 __repr__,具體用法見:Python魔法方法詳解-基本的魔法方法
例如:
>>> class A: def __str__(self): return("你好,來自江南的你!") >>> a = A() >>> print(a) 你好,來自江南的你! >>> a <__main__.A object at 0x000002D0B1F65390>
__str__ 用在被列印的時候需要以字串的形式輸出的時候,就會找到 __str__魔法方法,然後把返回的值打印出來。
下面是__repr__的示例:
>>> class B:
def __repr__(self):
return "你好,來自江南的你!"
>>> b = B()
>>> b
你好,來自江南的你!
下面程式碼走起:
import time as t
class MyTimer:
def __init__(self):
self.unit = ['年', '月', '天', '小時', '分鐘', '秒']
self.prompt = "未開始計時!"
self.lasted = []
self.begin = 0
self.end = 0
def __str__(self):
return self.prompt
__repr__ = __str__
def __add__(self, other):
prompt = "總共運行了"
result = []
for index in range(6):
result.append(self.lasted[index] + other.lasted[index])
if result[index]:
prompt += (str(result[index]) + self.unit[index])
return prompt
# 開始計時
def start(self):
self.begin = t.localtime()
self.prompt = "提示:請先呼叫 stop() 停止計時!"
print("計時開始...")
# 停止計時
def stop(self):
if not self.begin:
print("提示:請先呼叫 start() 進行計時!")
else:
self.end = t.localtime()
self._calc()
print("計時結束!")
# 內部方法,計算執行時間
def _calc(self):
self.lasted = []
self.prompt = "總共運行了"
for index in range(6):
self.lasted.append(self.end[index] - self.begin[index])
if self.lasted[index]:
self.prompt += (str(self.lasted[index]) + self.unit[index])
# 為下一輪計時初始化變數
self.begin = 0
self.end = 0
進階定製
- 如果開始計時的時間是(2022年2月22日16:30:30),停止時間是(2025年1月23日15:30:30),那按照我們用停止時間減開始時間的計算方式就會出現負數(3年-1月1天-1小時),你應該對此做一些轉換。
- 現在的計算機速度都非常快,而我們這個程式最小的計算單位卻只是秒,精度是遠遠不夠的。(看下面的課後作業)
動動手
0. 按照課堂中的程式,如果開始計時的時間是(2022年2月22日16:30:30),停止時間是(2025年1月23日15:30:30),那按照我們用停止時間減開始時間的計算方式就會出現負數,你應該對此做一些轉換。
import time as t
class MyTimer:
def __init__(self):
self.unit = ['年', '月', '天', '小時', '分鐘', '秒']
self.borrow = [0, 12, 31, 24, 60, 60]
self.prompt = "未開始計時!"
self.lasted = []
self.begin = 0
self.end = 0
def __str__(self):
return self.prompt
__repr__ = __str__
def __add__(self, other):
prompt = "總共運行了"
result = []
for index in range(6):
result.append(self.lasted[index] + other.lasted[index])
if result[index]:
prompt += (str(result[index]) + self.unit[index])
return prompt
# 開始計時
def start(self):
self.begin = t.localtime()
self.prompt = "提示:請先呼叫 stop() 停止計時!"
print("計時開始...")
# 停止計時
def stop(self):
if not self.begin:
print("提示:請先呼叫 start() 進行計時!")
else:
self.end = t.localtime()
self._calc()
print("計時結束!")
# 內部方法,計算執行時間
def _calc(self):
self.lasted = []
self.prompt = "總共運行了"
for index in range(6):
temp = self.end[index] - self.begin[index]
# 低位不夠減,需向高位借位
if temp < 0:
# 測試高位是否有得“借”,沒得借的話向再高位借......
i = 1
while self.lasted[index-i] < 1:
self.lasted[index-i] += self.borrow[index-i] - 1
self.lasted[index-i-1] -= 1
i += 1
self.lasted.append(self.borrow[index] + temp)
self.lasted[index-1] -= 1
else:
self.lasted.append(temp)
# 由於高位隨時會被借位,所以列印要放在最後
for index in range(6):
if self.lasted[index]:
self.prompt += str(self.lasted[index]) + self.unit[index]
# 為下一輪計時初始化變數
self.begin = 0
self.end = 0
1. 相信大家已經意識到不對勁了:為毛一個月一定要31天?不知道有可能也是30天或者29天嗎?(上一題我們的答案是假設一個月31天)
沒錯,如果要正確得到月份的天數,我們還需要考慮是否閏年,還有每月的最大天數,所以太麻煩了……如果我們不及時糾正,我們會在錯誤的道路上越走越遠……
所以,這一次,小甲魚提出了更優秀的解決方案(Python官方推薦):用 time 模組的 perf_counter() 和 process_time() 來計算,其中 perf_counter() 返回計時器的精準時間(系統的執行時間); process_time() 返回當前程序執行 CPU 的時間總和。
題目:改進我們課堂中的例子,這次使用 perf_counter() 和 process_time() 作為計時器。另外增加一個 set_timer() 方法,用於設定預設計時器(預設是 perf_counter(),可以通過此方法修改為 process_time())。
import time as t
class MyTimer:
def __init__(self):
self.prompt = "未開始計時!"
self.lasted = 0.0
self.begin = 0
self.end = 0
self.default_timer = t.perf_counter
def __str__(self):
return self.prompt
__repr__ = __str__
def __add__(self, other):
result = self.lasted + other.lasted
prompt = "總共運行了 %0.2f 秒" % result
return prompt
# 開始計時
def start(self):
self.begin = self.default_timer()
self.prompt = "提示:請先呼叫 stop() 停止計時!"
print("計時開始...")
# 停止計時
def stop(self):
if not self.begin:
print("提示:請先呼叫 start() 進行計時!")
else:
self.end = self.default_timer()
self._calc()
print("計時結束!")
# 內部方法,計算執行時間
def _calc(self):
self.lasted = self.end - self.begin
self.prompt = "總共運行了 %0.2f 秒" % self.lasted
# 為下一輪計時初始化變數
self.begin = 0
self.end = 0
# 設定計時器(time.perf_counter() 或 time.process_time())
def set_timer(self, timer):
if timer == 'process_time':
self.default_timer = t.process_time
elif timer == 'perf_counter':
self.default_timer = t.perf_counter
else:
print("輸入無效,請輸入 perf_counter 或 process_time")
2. 既然咱都做到了這一步,那不如再深入一下。再次改進我們的程式碼,讓它能夠統計一個函式執行若干次的時間。
要求一:函式呼叫的次數可以設定(預設是 1000000 次)
要求二:新增一個 timing() 方法,用於啟動計時器
函式演示:
>>> ================================ RESTART ================================
>>>
>>> def test():
text = "I love FishC.com!"
char = 'o'
if char in text:
pass
>>> t1 = MyTimer(test)
>>> t1.timing()
>>> t1
總共運行了 0.27 秒
>>> t2 = MyTimer(test, 100000000)
>>> t2.timing()
>>> t2
總共運行了 25.92 秒
>>> t1 + t2
'總共運行了 26.19 秒'
程式碼清單:
import time as t
class MyTimer:
def __init__(self, func, number=1000000):
self.prompt = "未開始計時!"
self.lasted = 0.0
self.default_timer = t.perf_counter
self.func = func
self.number = number
def __str__(self):
return self.prompt
__repr__ = __str__
def __add__(self, other):
result = self.lasted + other.lasted
prompt = "總共運行了 %0.2f 秒" % result
return prompt
# 內部方法,計算執行時間
def timing(self):
self.begin = self.default_timer()
for i in range(self.number):
self.func()
self.end = self.default_timer()
self.lasted = self.end - self.begin
self.prompt = "總共運行了 %0.2f 秒" % self.lasted
# 設定計時器(time.perf_counter() 或 time.process_time())
def set_timer(self, timer):
if timer == 'process_time':
self.default_timer = t.process_time
elif timer == 'perf_counter':
self.default_timer = t.perf_counter
else:
print("輸入無效,請輸入 perf_counter 或 process_time")
學習完這一節課後,我想告訴大家一件事,其實,關於 Python 程式碼優化你需要知道的最重要問題是,決不要自己編寫計時函式!!!!!
為一個很短的程式碼計時都很複雜,因為你不知道處理器有多少時間用於執行這個程式碼?有什麼在後臺執行?小小的疏忽可能破壞你的百年大計,後臺服務偶爾被 “喚醒” 在最後千分之一秒做一些像查收信件,連線計時通訊伺服器,檢查應用程式更新,掃描病毒,檢視是否有磁碟被插入光碟機之類很有意義的事。在開始計時測試之前,把一切都關掉,斷開網路的連線。再次確定一切都關上後關掉那些不斷檢視網路是否恢復的服務等等。
接下來是計時框架本身引入的變化因素。Python 直譯器是否快取了方法名的查詢?是否快取程式碼塊的編譯結果?正則表示式呢? 你的程式碼重複執行時有副作用嗎?不要忘記,你的工作結果將以比秒更小的單位呈現,你的計時框架中的小錯誤將會帶來不可挽回的結果扭曲。
Python 社群有句俗語:“Python 自己帶著電池。” 別自己寫計時框架。Python 具備一個叫做 timeit 的完美計時工具。
或許你現在怨恨我為什麼不早點說,但如果你在這節課的折騰中已經掌握了類的定製和使用,那我的目的就達到了。接下來,請認真閱讀更為專業的計時器用法及實現原始碼:Python擴充套件閱讀:timeit 模組詳解(準確測量小段程式碼的執行時間),