有時候,我們想實現一個非常簡單的定時功能,例如讓一個程序每天早上8點調用某個函數。但我們又不想安裝任何第三方庫,也不會使用 crontab 或者任務計劃功能,就想使用純 Python 來實現。
可能有同學會這樣寫代碼:
import time
import datetime
def run():
print('我是需要被每天調用的函數')
def schedule():
target_time = datetime.time(8, 0, 0)
today = datetime.date.today()
target_date = today + datetime.timedelta(days=1)
target_datetime = datetime.datetime.combine(target_date, target_time)
now = datetime.datetime.now()
delta = (target_datetime - now).total_seconds()
time.sleep(delta)
run()
while True:
time.sleep(24 * 3600)
run()
if __name__ == '__main__':
schedule()這段程序,首先計算出現在距離明天早上8點相差的秒數。睡這麼多秒以後,第一次運行目標函數。然後進入一個死循環,每隔86400秒,程序調用一次 run 函數。
這個程序初看起來,似乎沒有什麼問題。但如果你每天觀察它的運行時間,你會發現隨著時間的推移,時間會越來越不準確。
這是因為,run 函數不是一瞬間就運行完成的。它運行也會消耗時間。假設程序第一次運行 run 函數的時候,確實剛剛好是8:00,run 函數運行了2秒。那麼,程序睡眠86400秒以後,時間實際上是8:00:02.從第二天開始,每天晚2秒鐘。一個月就會晚一分鐘。
但實際上,我們如果付出一點點微不足道的代價,我們就可以防止這種誤差的發生,並且程序代碼會變得更簡單:
import time
import datetime
def run():
print('我是需要被每天調用的函數')
def schedule():
last_run = None
while True:
now = datetime.datetime.now()
if now.strftime('%H:%M') == '08:00' and last_run != now.date():
run()
last_run = now.date()
time.sleep(1)
if __name__ == '__main__':
schedule()程序在一個死循環中,每秒做一次檢查,如果當前的時分正好是08:00,並且上一次運行不是今天,那麼就調用 run 函數,並把上一次運行的時間設置為今天。否則,就睡眠1秒鐘。
這樣做,相當於每秒都會校對時間,從而避免了長時間運行導致的時間誤差。雖然看起來這個死循環會非常消耗 CPU,但只要你算一下,實際上它只不過每天循環86400次而已。這個次數並不多。
但無論如何,專業的事情應該交由專業的工具來做。time.sleep用來設置周期性的時間間隔可以,但它實際上不適合用來做定時任務。
因為一個支持定時任務的庫,例如 Python 的schedule或者APScheduler,他們在確保定時時間準確上,做了很多工作。還有一些庫甚至用到了時間輪這樣的數據結構來確保時間的準確性。這不是我們簡單用兩三行 Python 代碼就能完成的。
總結如果能用 crontab 或者任務計劃,那麼這是最優選擇。其次,使用 Python 專用的定時模塊。最次,才是使用 time.sleep 來實現。如果不得不用 time.sleep,那麼應該儘量縮短檢查的間隔,避免長時間睡眠。
更多開發中的坑與避坑知識點,請關注我的課程《Python 業務開發 常見錯誤案例集》:
Long-press QR code to transfer me a reward
攢錢給產品經理買房。
As required by Apple's new policy, the Reward feature has been disabled on Weixin for iOS. You can still reward an Official Account by transferring money via QR code.