註:本文同步刊載在Medium,若習慣Medium的話亦可去那邊看呦!
我們先來解一下昨天的練習吧!
>>> from datetime import date, datetime, time ,timedelta
>>> now = date(2020, 10, 1)
>>> mem = [date(2021,8,14), date(2021,2,14), date(2021,3,14), date(2020,10,3), d
ate(2021,11,3)]
>>> diff = sorted([d - now for d in mem]) # 你沒看錯,是可以排序的~
>>> diff
[datetime.timedelta(days=2), datetime.timedelta(days=136), datetime.timedelta(da
ys=164), datetime.timedelta(days=317), datetime.timedelta(days=398)]
>>> for d in diff:
... print(str(d)) # 這樣顯示也可以啦!但好像可以簡單一點XD
...
2 days, 0:00:00
136 days, 0:00:00
164 days, 0:00:00
317 days, 0:00:00
398 days, 0:00:00
>>> days = [d.days for d in diff] # deltatime也可以單取其中的單位
>>>
>>> days
[2, 136, 164, 317, 398]
糟糕,這篇文貼出來的時間點第一個重要日子已經過了,
只好希望小亦有記得阿啾的生日了XD
上一篇我們提到了可以利用time.time()來計算,
但如果要取平均的話,可能要自己寫迴圏來處理,
但寫迴圏這件事情從根本上就有些破壞原本的結構,
那麼該怎麼作比較好呢?
我們可以利用timeit這個模組。
當我們想簡單計算某個指令的耗時的時候,
我們可以先用直接在命令提示字元裡下指令的方法,
比方說:
C:\Users\Desolve\utils>python -m timeit "'-'.join(str(n) for n in range(100))" # 今晚,我想來點字串組合套餐
10000 loops, best of 5: 23 usec per loop
"python -m" 可以讓我們得以呼叫一些標準函式庫的方法並輸入一些參數,
以本例來說,就是呼叫timeit,
然後要求它測試將0~99轉成字串並用"-"連接起來的耗時。
我們可以看到在沒有預設的狀況下,它跑了10000次,
並且取到最快的5次平均,結果平均速度是23微秒。
這個相當於我們在直譯器或.py檔中使用timeit.timeit(),
其方法為:
timeit.timeit(stmt, setup, timer, number),
stmt: 被量測的程式碼
setup: 開始前可能有一些前置的設定要做的話就放在這裡
timer: 請忽視,這是預設的timer
number: stmt執行的次數
>>> import timeit
>>> timeit.timeit("'-'.join(str(n) for n in range(100))")
... # 然後你就會發現卡住了,因為在這裡預設的次數是1000000次(一百萬次)
# 連續按Ctrl+C以中斷上面的執行
>>> timeit.timeit("'-'.join(str(n) for n in range(100))", number=10000)
0.24359135900000695
# 需要多行的話,請使用分號分隔不同行
>>> timeit.timeit(stmt='x=3;y=5;res=x*y')
0.04275195899998607
如果想要重複timeit的測量,可以使用timeit.repeat():
# 相當於做timeit.timeit() 7遍
>>> timeit.repeat(stmt='x=3;y=5;res=x*y', number=10000, repeat=7)
[0.0004259219999767083, 0.0004217530000687475, 0.0004227150000133406, 0.00042143
199993915914, 0.00043714699995689443, 0.0004217530000687475, 0.00044612800002141
74]
如果是要測試一整個函式,我們是可以將整段都塞進字串裡沒錯,
但看起來太醜了XD!
那麼,怎麼樣在不動到原有的程式碼的狀況下新增進行測試呢?
我們可以使用類似如下的方式:
import timeit
def f():
import os
for path, dirs, files in os.walk('.'):
print(path)
for f in files:
print(os.path.join(path, f))
for d in dirs:
print(os.path.join(path, d))
# 下面這三行的做的事情是一樣的
print(timeit.timeit(f, number=5))
# print(timeit.timeit('f()', setup='from __main__ import f', number=5))
# print(timeit.timeit('f()', globals=globals(), number=5))
其結果如下:
C:\Users\Desolve\utils>python fromzero
.
.\bookstore.json
.\check.py
.\fromzero.py
.\poem.txt
.\schedule.py
.\__init__.py
.\csv
.\json
.\__pycache__
.\csv
.\csv\student.csv
.\csv\student_dic.csv
.\json
.\json\classA.json
.\__pycache__
.\__pycache__\check.cpython-38.pyc
.\__pycache__\schedule.cpython-38.pyc
.\__pycache__\__init__.cpython-38.pyc
...(會再重複4次)
0.008256415
stmt也可以直接傳入函數名稱,這時候後面就不用加括號了,
因為是當成一個函數物件;
如果當成一個字串來傳進去的話,
就必須要在setup的時後告訴它主程式有這個東西,
這樣Python才認得到呦!
(也就是__main__)
或者,也可以使用globals=globals(),
直接將所有的程式碼當成全域來用,也可以達到相同的效果。
昨天到今天這兩篇,應該可以幫助讀者對於時間相關的方法有初步的認識。
事實上要再深究的話,執行時間還會受到一些額外的影響,
比如Python的gc(資源回收處理),執行程式時電腦其他程式占用CPU資源的影響等,
但這比較深入一些,我們就不在這裡細究了XD!
(註:timeit預設會把gc關掉,如果要測打開的狀況,
可以在setup放入'gc.enable()'。)
最後我們也來做個練習吧!
還記得先前我們講遞迴嗎(第九篇)?
我們後面有做過練習,在第十篇有解答。
我們比較過沒有做好的遞迴的一個解答,
跟有記錄前面答案的遞迴,且提供了二個解答;
請比較這三種在做n=35的時候的計算時速度的差異。
(注意:只做n=35即可,不用算1~35,此外number取10次就好。)
那麼,我們就明天見囉!