第044講:魔法方法:簡單定製
課題筆記:
這節課完成一個類的定製:
效果:
__str__() 就是用在被列印的時候需要字串形式輸出的時候,就會找到這個魔法方法。然後把它返回的值給打印出來。
>>> class A(): def __str__(self): return "hale" >>> a = A() >>> print(a) hale >>> a <__main__.A object at 0x00000281B66C33A0> >>> class B(): def __repr__(self): return "Hale" >>> b = B() >>> b Hale >>>
所以重寫這2個魔法方法就可以做到這些要的功能。
time模組:[擴充套件閱讀] time 模組詳解(時間獲取和轉換)
先試寫了,使用pycharm需要設定斷點? 但是不知道在哪裡設定,如何設定。
import time as t #首先我們需要time的模組,就先匯入 class MyTimer(): #開始計時 def start(self): self.star = t.localtime()print('計時開始...') #停止計時 def stop(self): self.stop = t.localtime() self._calc() print('計時結束!') #到此地基寫好了,考慮下怎麼計算,直接相減? localtime返回的是一個元祖的結構 #內部方法,計算執行時間。 def _calc(self): self.lasted = [] self.prompt = '總共運行了' #這是提示 for index in range(6):#要用前面的6個元素,就是localtime返回的元組結構 self.lasted.append(self.stop[index] - self.start[index]) self.prompt += str(self.lasted[index]) print(self.prompt)
找到錯誤了,是def start的裡面少了個t
改正:
1 import time as t #首先我們需要time的模組,就先匯入 2 3 class MyTimer(): 4 #開始計時 5 def start(self): 6 self.start = t.localtime() 7 print('計時開始...') 8 9 #停止計時 10 def stop(self): 11 self.stop = t.localtime() 12 self._calc() 13 print('計時結束!') 14 15 #到此地基寫好了,考慮下怎麼計算,直接相減? localtime返回的是一個元祖的結構 16 17 #內部方法,計算執行時間。 18 def _calc(self): 19 self.lasted = [] 20 self.prompt = '總共運行了' #這是提示 21 for index in range(6):#要用前面的6個元素,就是localtime返回的元組結構 22 self.lasted.append(self.stop[index] - self.start[index]) 23 self.prompt += str(self.lasted[index]) 24 25 print(self.prompt)實驗版本
接下來就是加功能,print(t1) 還有直接呼叫t1它會顯示結果,需要重寫str和repr這2個魔法方法來實現:
如果這裡不按常理出牌,:
>>> t1 = MyTimer() >>> t1 Traceback (most recent call last): File "<pyshell#45>", line 1, in <module> t1 File "C:\Users\1\AppData\Local\Programs\Python\Python38\lib\idlelib\rpc.py", line 620, in displayhook text = repr(value) File "D:/python練習/MyTimer.py", line 5, in __str__ return self.prompt AttributeError: 'MyTimer' object has no attribute 'prompt'
當我們直接呼叫t1的時候,Python會直接找到這個str的魔法方法,找到repr這個魔法方法,而repr又等於str,所以會找到str方法。str魔法方法裡面呼叫了self.prompt, 那它沒定義,它在哪裡定義呢?
在calc裡面的賦值那,賦值就是定義。那一來就讓他顯示出來肯定不行,要先通過start 在stop,然後才被賦值,才被定義,才能夠使用它。所以所有屬於示例物件的變數在init裡面先定義就不會出現問題了、。
測試:
t1 = MyTimer() t1.start() Traceback (most recent call last): File "<input>", line 1, in <module> TypeError: 'int' object is not callable
又出錯了汗。。。。屬性名和方法名重複了!!!!
初始化時候,屬性覆蓋了方法!錯誤型別就是整形不能被呼叫。
改下:
import time as t #首先我們需要time的模組,就先匯入 class MyTimer(): def __init__(self): self.prompt = '未開始計時' self.lasted = [] self.begin = 0 self.end = 0 def __str__(self): return self.prompt __repr__ = __str__ #開始計時 def start(self): self.begin = t.localtime() print('計時開始...') #停止計時 def stop(self): self.end = t.localtime() self._calc() print('計時結束!') #到此地基寫好了,考慮下怎麼計算,直接相減? localtime返回的是一個元祖的結構 #內部方法,計算執行時間。 def _calc(self): self.lasted = [] self.prompt = '總共運行了' #這是提示 for index in range(6):#要用前面的6個元素,就是localtime返回的元組結構 self.lasted.append(self.end[index] - self.begin[index]) self.prompt += str(self.lasted[index])屬性和方法名不能一樣
接下來就是顯示的結果問題,顯示000002不合適,我們希望顯示年月日時分秒,值為0就不顯示。
我們新增一個列表用來存放對應的單位,只顯示有資料的單位
1 import time as t #首先我們需要time的模組,就先匯入 2 3 class MyTimer(): 4 def __init__(self): 5 self.unit = ['年', '月', '日', '小時', '分鐘', '秒'] 6 self.prompt = '未開始計時' 7 self.lasted = [] 8 self.begin = 0 9 self.end = 0 10 def __str__(self): 11 return self.prompt 12 13 __repr__ = __str__ 14 15 #開始計時 16 def start(self): 17 self.begin = t.localtime() 18 print('計時開始...') 19 20 #停止計時 21 def stop(self): 22 self.end = t.localtime() 23 self._calc() 24 print('計時結束!') 25 26 #到此地基寫好了,考慮下怎麼計算,直接相減? localtime返回的是一個元祖的結構 27 28 #內部方法,計算執行時間。 29 def _calc(self): 30 self.lasted = [] 31 self.prompt = '總共運行了' #這是提示 32 for index in range(6):#要用前面的6個元素,就是localtime返回的元組結構 33 self.lasted.append(self.end[index] - self.begin[index]) 34 if self.lasted[index] : #這邊只顯示索引不為0的,也就是顯示有數值的單位 35 self.prompt += (str(self.lasted[index]) + self.unit[index])顯示結果有單位,只顯示有資料的單位
需要在適當的地方新增一些溫馨提示,例如:還沒有開始你就點選了stop,提示先執行start開始計時。
import time as t #首先我們需要time的模組,就先匯入 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 start(self): self.begin = t.localtime() print('計時開始...') self.prompt = '提示:請先呼叫stop()停止計時!' #停止計時 def stop(self): if not self.begin: print('請先呼叫start()進行計時!') else: self.end = t.localtime() self._calc() print('計時結束!') #到此地基寫好了,考慮下怎麼計算,直接相減? localtime返回的是一個元祖的結構 #內部方法,計算執行時間。 def _calc(self): self.lasted = [] self.prompt = '總共運行了' #這是提示 for index in range(6):#要用前面的6個元素,就是localtime返回的元組結構 self.lasted.append(self.end[index] - self.begin[index]) if self.lasted[index] : #這邊只顯示索引不為0的,也就是顯示有數值的單位 self.prompt += (str(self.lasted[index]) + self.unit[index]) #為下一輪計時初始化變數 self.begin = 0 self.end = 0增加溫馨提示,最後重新初始化變數為下一輪計時
結果:
>>> runfile('D:/python練習/MyTimer.py', wdir='D:/python練習') t1 = MyTimer() t1 未開始計時 t1.stop() 請先呼叫start()進行計時! t1.start() 計時開始... t1 提示:請先呼叫stop()停止計時! t1.stop() 計時結束! t1 總共運行了8秒
最後重寫一下add魔法方法,讓2個計時器的物件相加會自動返回他們的時間和。
import time as t #首先我們需要time的模組,就先匯入 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 = "總共運行了" #這裡沒有self! 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() print('計時開始...') self.prompt = '提示:請先呼叫stop()停止計時!' #停止計時 def stop(self): if not self.begin: print('請先呼叫start()進行計時!') else: self.end = t.localtime() self._calc() print('計時結束!') #到此地基寫好了,考慮下怎麼計算,直接相減? localtime返回的是一個元祖的結構 #內部方法,計算執行時間。 def _calc(self): self.lasted = [] self.prompt = '總共運行了' #這是提示 for index in range(6):#要用前面的6個元素,就是localtime返回的元組結構 self.lasted.append(self.end[index] - self.begin[index]) if self.lasted[index] : #這邊只顯示索引不為0的,也就是顯示有數值的單位 self.prompt += (str(self.lasted[index]) + self.unit[index]) #為下一輪計時初始化變數 self.begin = 0 self.end = 0修改了add魔法方法
結果:
t1 = MyTimer() t1.start() 計時開始... t1.stop() 計時結束! t1 總共運行了4秒 t2 = MyTimer() t2.start() 計時開始... t2.stop() 計時結束! t2 總共運行了4秒 t1 + t2 '總共運行了8秒'
有1分鐘-30秒是什麼意思,
動動手:
0. 按照課堂中的程式,如果開始計時的時間是(2022年2月22日16:30:30),停止時間是(2025年1月23日15:30:30),那按照我們用停止時間減開始時間的計算方式就會出現負數,你應該對此做一些轉換。
思路:全部轉化為秒嗎? 我的做法是加一個數組,
有負數出現就補充相應的一整個單位,且前一個時間單位減1.
答:參考程式碼寫的比較“糾結”,期待魚油們寫出更漂亮的實現。
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 #預設初始化的計時器為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): 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 的完美計時工具。
或許你現在怨恨小甲魚為什麼不早點說,但如果你在這節課的折騰中已經掌握了類的定製和使用,那小甲魚的目的就達到了。接下來,請認真閱讀更為專業的計時器用法及實現原始碼:【擴充套件閱讀】timeit 模組詳解(準確測量小段程式碼的執行時間)