近來感覺自己編碼動手能力實在太差,平時又沒有什麼編程的點子,但又不能學而不練,於是想到要不自己來解讀一些開源代碼並把分析過程編輯成博客吧。遂有了這篇博客。這將會是一系列的源碼解讀博客,這是該系列的第一篇,源碼來源一般爲 Github
等網站。
關於閱讀代碼這回事,個人覺得和閱讀文章和書籍十分相像。我認爲一位好的作家肯定是一位好的讀者,如果想要讓自己寫出精彩的文字,那麼肯定少不了大量地閱讀其他人寫下來的精彩的文字。這個道理放在編程中,我覺得也一樣。
本人在編程方面還是一個新手,這也是我頭一次這麼正經地分析代碼,分析代碼能力也處於新手水平。雖然這次分析的代碼很簡單,但如果大家發現任何問題的話,還請指出。
開始分析代碼吧。
代碼
這個 repo
裏有許多作者寫的用來解決自己日常問題的 Python 小工具,在這裏也向大家推薦這個 repo
。
我故意挑了一個這個 repo
中的一個簡單的小工具,一個簡單的計時器。
雖然程序很簡單,但用到的 Python 語法工具卻還挺多,比如 time 內置模塊、異常處理等。
# Author: OMKAR PATHAK
# This script helps to build a simple stopwatch application using Python's time module.
import time
print('Press ENTER to begin, Press Ctrl + C to stop')
while True:
try:
input() # For ENTER. Use raw_input() if you are running python 2.x instead of input()
starttime = time.time()
print('Started')
while True:
print('Time Elapsed: ', round(time.time() - starttime, 0), 'secs', end="\r")
time.sleep(1) # 1 second delay
except KeyboardInterrupt:
print('Stopped')
endtime = time.time()
print('Total Time:', round(endtime - starttime, 2), 'secs')
break
代碼分析
這個程序導入了一個 time 模塊。
程序運行會先打印一條語句:Press ENTER to begin, Press Ctrl + C to stop
然後進入一個 while 無限循環。
在第一個 while 循環內部使用了一組 try-except 語句塊,我們來看看。不過在此之前,對於這麼一個無限的 while 循環,會很自然地產生了一個問題:這個循環的終止條件是什麼?一般來說,想要退出這個循環,需要使用 break語句,所以我們找一找代碼中有沒有 break 語句。果然,break 語句在代碼的最後一行。這也就意味着,當程序出現一個稱爲 KeyboardInterrupt
異常時,最外層的 while 循環(第7行)就會終止。
接着來看 try 語句塊內部。input()
函數大家都不陌生,這裏的 input()
函數的作用其實是等待一個回車鍵輸入(我很好奇,input() 函數收到一個回車鍵輸入後,底層會發生什麼事,但這次先不討論這個問題)。收到一個回車鍵輸入後,這個計時小程序開始計時。
作者聲明瞭一個 starttime
變量。在這裏首先得解釋一下 time 模塊中 time()
函數。time() 會返回當前時間的時間戳。什麼是時間戳?
時間戳是指格林威治時間自1970年1月1日(00:00:00 GMT)至當前時間的總秒數 [來源]
ok,也就是說調用 time() 函數我們會得到1970年1月1日0時0分到我們當前時間的總秒數,比如像下面這樣:
>>> import time
>>> time.time() # current time: 2020-04-11
1586593438.927243
所以,starttime
變量會的值爲程序運行到這條語句時的時間戳。
然後是第 11 行,打印一句話,沒什麼好說的。
第 12 行,又進入一個無限的 while 循環。循環體中,會先打印一條語句,然後調用 time 模塊的 sleep() 函數,無限重複這個過程。sleep() 的功能很簡單,讓系統休眠指定秒數,在程序中則是讓系統休眠 1 秒。主要是看第 13 行語句,也就是打印輸出的這條語句。在語句中用到了 Python 內置的 round()
函數,對於這個函數,其實輸出結果理解起來很簡單:輸入一個數值(一般爲浮點數),返回這個數值的四捨五入的結果,如:
>>> round(1.2)
1
>>> round(1.5)
2
>>> round(1)
1
round() 函數還可以接收另一個參數,用這個參數可以指定保留小數點後幾位,示例如下:
>>> round(1.22222, 2)
1.22
>>> round(1.22666, 3)
1.227
但在某些情況下這個函數的輸出結果並不符合四捨五入這個法則,對於這個問題,在這裏就不討論了,感興趣的朋友可以參考這些資料:round 函數用法,關於 round 函數的小坑
繼續分析程序,print() 語句中使用了 round() 函數作爲參數,我們來看看 round() 函數的其中一個參數:
time.time() - starttime
這個參數的結果就是秒數,當前時間減去計時開始時間。
try
語句塊分析完了,下面來看 except
語句塊。
except 塊接到 KeyboardInterrupt
後,會打印一條語句,Stopped。然後聲明一個 endtime
變量,將程序出現異常時的時間戳賦給該變量。然後打印一條語句,又用到了一個 round() 函數,第一個參數中,endtime
減去 starttime
,結果就是計時器所記錄的總秒數。round()的第二個參數表示結果保留 2 位小數。
最後,調用break語句,結束最外層的while循環(第7行)循環,程序結束。
程序分析完了,來看看程序運行的結果吧。
源碼思路分析
分析一下這個程序的實現思路。
程序的功能就是個簡單的控制檯計時器。個人認爲最外層的 while 循環可以不加。不太理解爲什麼作者要在最外面也加一個 while 循環。
計時開始。第 9 行 input()
函數的作用好比是計時器的開始按鈕(總不能剛一運行程序就開始計時吧)。按下按鈕後,記錄一個開始時間,然後開始計時。計時的功能通過 while
循環加上 time
模塊的 sleep()
來實現(第12 - 14行)。邊計時邊打印已經過了幾秒。
那麼如何停止計時呢?按下 ctrl + c,系統會拋出一個 KeyboardInterrupt
異常,計時隨之停止。作者編寫了一個 except 語句塊來處理這個異常,最終輸出總秒數。
運行程序
這一小節記錄了程序在我的計算機中運行的結果,以及我產生的一些疑問。
在我的計算機的(win10,64位操作系統)IDLE 中程序輸出是這樣:
這個輸出有些醜陋,但將輸出複製到 markdown 文檔裏後,會變成下面這樣,目前不清楚是什麼原因:
Press ENTER to begin, Press Ctrl + C to stop
Started
Time Elapsed: 0.0 secs
Time Elapsed: 1.0 secs
Time Elapsed: 2.0 secs
Time Elapsed: 3.0 secs
Time Elapsed: 4.0 secs
Time Elapsed: 5.0 secs
Stopped
Total Time: 6.26 secs
這個小程序我在 PyCharm 中運行了一下,運行過程是這樣的:
按下 stop 按鈕後的結果:
很奇怪,爲什麼在 PyCharm 中的輸出是這樣的效果?
(完)