1. 程式人生 > 程式設計 >在 Python 中接管鍵盤中斷訊號的實現方法

在 Python 中接管鍵盤中斷訊號的實現方法

在 Python 中接管鍵盤中斷訊號的實現方法

假設有這樣一個需求,你需要從 Redis 中持續不斷讀取資料,並把這些資料寫入到 MongoDB 中。你可能會這樣寫程式碼:

import json 
import redis 
import pymongo 
client = redis.Redis() 
handler = pymongo.MongoClient().example.col 
while True: 
  data_raw = client.blpop('data',timeout=300) 
  if not data_raw: 
    continue 
  data = json.loads(data_raw[1].decode()) 
  handler.insert_one(data) 

但這樣寫有一個問題,就是每來一條資料都要連線一次 MongoDB,大量時間浪費在了網路 I/O上。

於是大家會把程式碼改成下面這樣:

import json 
import redis 
import pymongo 
client = redis.Redis() 
handler = pymongo.MongoClient().example.col 
to_be_insert = [] 
while True: 
  data_raw = client.blpop('data',timeout=300) 
  if not data_raw: 
    continue 
  data = json.loads(data_raw[1].decode()) 
  to_be_insert.append(data) 
  if len(to_be_insert) >= 1000: 
    handler.insert_many(to_be_insert) 
    to_be_insert = [] 

每湊夠1000條資料,批量寫入到 MongoDB 中。

現在又面臨另外一個問題。假設因為某種原因,我需要更新這個程式,於是我按下了鍵盤上的Ctrl + C強制關閉了這個程式。而此時to_be_insert列表裡面有999條資料將會永久丟失——它們已經被從 Redis 中刪除了,但又沒有來得及寫入 MongoDB 中。

我想實現,當我按下 Ctrl + C 時,程式不再從 Redis 中讀取資料,但會先把to_be_insert中的資料(無論有幾條)都插入 MongoDB 中。最後再關閉程式。

要實現這個需求,就必須在我們按下Ctrl + C時,程式還能繼續執行一段程式碼。可問題是按下Ctrl + C時,程式就直接結束了,如何還能再執行一段程式碼?

實際上,當我們按下鍵盤上的Ctrl + C時,Python 收到一個名為SIGINT的訊號。具體規則可以閱讀官方文件。收到訊號以後,Python 會呼叫一個訊號回撥函式。只不過預設的回撥函式就是讓程式丟擲一個 KeyboardInterrupt異常導致程式關閉。現在,我們可以設法讓 Python 使用我們自定義的一段函式來作為訊號回撥函式。

要使用訊號,我們需用匯入 Python 的signal庫。然後自定義一個訊號回撥函式,當 Python 收到某個訊號時,呼叫這個函式。

所以我們修改一下上面的程式碼:

import signal 
import json 
import redis 
import pymongo 
 
 
client = redis.Redis() 
handler = pymongo.MongoClient().example.col 
stop = False 
 
 
def keyboard_handler(signum,frame): 
  global stop 
  stop = True 
 
 
signal.signal(signal.SIGINT,keyboard_handler) 
 
to_be_insert = [] 
while not stop: 
  data_raw = client.blpop('data',timeout=300) 
  if not data_raw: 
    continue 
  data = json.loads(data_raw[1].decode()) 
  to_be_insert.append(data) 
  if len(to_be_insert) >= 1000: 
    handler.insert_many(to_be_insert) 
    to_be_insert = [] 
 
if to_be_insert: 
  handler.insert_many(to_be_insert) 

我們定義了一個全域性變數stop,預設為 False,所以預設情況下,while not stop所在的迴圈體會持續執行。

我們定義了一個函式keyboard_handler,它的作用是修改全域性變數stop為 True。需要注意的是,在函式裡面修改全域性變數,必須先使用global 變數名宣告這個變數為全域性變數。否則無法修改。

修改以後,while not stop迴圈停止,於是程式進入:

if to_be_insert: 
  handler.insert_many(to_be_insert) 

只要列表裡面有資料,就會批量插入 MongoDB 中。然後程式結束。

整段程式碼的關鍵就在signal.signal(signal.SIGINT,keyboard_handler)這裡把訊號SIGINT與函式keyboard_handler關聯上了,於是,在上面這段程式碼執行的任何時候,只要按下鍵盤的Ctrl + C,程式就會進入keyboard_handler函式裡面,優先執行這個函式裡面的程式碼。執行完成以後,回到之前中斷的地方,繼續執行之前沒有完成的程式碼。而由於在函式裡面我已經修改了stop的值,所以原來的迴圈不能繼續執行,於是進入最後的收尾工作。

需要注意的是,如果你的整個程式碼全都是使用 Python 寫的,那麼 signal可以在你程式的任何階段觸發,只要你按下 Ctrl + C,立刻就會進入設定好的訊號回撥函式中。

但如果你的程式碼中,有一部分程式碼是使用 C 語言寫的,那麼當你按下Ctrl + C以後,可能需要等這段C 語言的程式碼執行完成以後,才會進入你設定的訊號回撥函式中。

總結

以上所述是小編給大家介紹的在 Python 中接管鍵盤中斷訊號的處理方法,希望對大家有所幫助!