PyQt信號槽的Python實現

最近看書的時候看到了觀察者模式的py實現,想到一直用的PyQt信號槽就是觀察者模式的升級版,想用python應該也能實現吧,就擼了一個。這裡先不發代碼,先介紹一下信號槽。

剛學Qt的時候就看書上說信號槽是Qt的核心機制,但後來PyQt擼多了後並沒有理解信號槽有多厲害,值得每本教程都放在首要位置介紹,現在回過頭來看看算是理解了這一點。

Qt是如何支持信號槽的

這裡有篇文章《How Qt Signals and Slots Work》講的很透徹,原文是這樣的:

The Qt signals/slots and property system are based on the ability to introspect the objects at runtime. Introspection means being able to list the methods and properties of an object and have all kinds of information about them such as the type of their arguments.

...

C++ does not offer introspection support natively, so Qt comes with a tool to provide it. That tool is MOC. It is a code generator (and NOT a preprocessor like some people call it).

譯:

Qt的信號槽和屬性系統的實現,需要對象運行時有自省的能力,自省就是能列出一個對象的方法和屬性,以及其所有信息,比如參數類型。

C++並沒有原生支持自省,因此Qt提供MOC這個工具來支持。MOC(Meta Object Compiler)是一個代碼生成器,並不像有些人說的是個預編譯器。

熟悉python的都明白,對於這種動態的解釋型語言,自省?那是天生就支持的東西,何況python里的函數還是第一類對象。因此,在我擼PyQt的時候,是根本意識不到支持信號槽需要花費這麼大的功夫的。可在奇趣公司開發Qt的時代,這些東西可能算是天頂星科技了吧。

這篇文章後面有信號槽實現的C++源代碼,我這裡就不列了,大家可以自己去看,不僅包括信號槽的實現,還包括了存取槽函數表時的加鎖操作,很值得一看。

PyQt信號槽 VS My信號槽

在PyQt中信號槽的寫法有幾種,有C++風格的寫法,我這裡列一下py風格的寫法。

#coding:utf-8from PyQt4.QtCore import pyqtSignal, QObjectclass QTypeSignal(QObject): sendmsg = pyqtSignal(object)#定義一個信號槽,傳入一個參數位參數 def __init__(self): QObject.__init__(self)#用super初始化會出錯 def run(self): self.sendmsg.emit(send)#發信號class QTypeSlot(object): def get(self, msg):#槽對象里的槽函數 print Qslot get msg, msgif __name__ == "__main__": send = QTypeSignal() slot = QTypeSlot() send.sendmsg.connect(slot.get) # 鏈接信號槽 send.run()#>>Qslot get msg send

這種信號槽的連接方式是python連接方式,和C++的風格不一樣。但即使是這樣,PyQt的信號槽還是帶有部分C++風格的,比如傳入信號的個數是用object的個數來定義的,在信號槽發送的類中必須繼承QObject,而且QObject初始化時還要小心。

那麼,來看看python的信號槽實現。

#coding:utf-8class MySignal(object): def __init__(self): self.collection = []#定義一個列表保存槽函數 def connect(self, fun): self.collection.append(fun)#添加槽函數 def emit(self, *args, **kwargs): for fun in self.collection: fun(*args, **kwargs)#遍歷執行槽函數class MyTypeSignal(object): sendmsg = MySignal()#實例化 def run(self): self.sendmsg.emit(send)#發送class MyTypeSlot(object): def get(self, msg):#槽對象里的槽函數 print My slot get msg, msgif __name__ == "__main__": send = MyTypeSignal() slot = MyTypeSlot() send.sendmsg.connect(slot.get)#鏈接信號槽 send.run()#>>My slot get msg send

MySignal類中定義了一個列表保存槽函數,在connect方法中綁定槽函數,在emit中遍歷執行,一個簡版的信號槽就實現了。這個信號槽支持python風格的參數,也不用為了多重繼承而煩心。

當然,這裡並沒有線程安全和disconnect方面的東西,需要時再添加也容易。寫這篇文章的目的也是想請教大家幫忙看看,有沒有什麼問題是我沒考慮到的,以及我理解不到位的。

下面是我的知乎專欄,歡迎關註:

python雜七雜八的使用經驗 - 知乎專欄

——————————————2017年5月16日————————————————————

更新了線程安全的版本,做過過線程安全的測試,在最近的使用中沒什麼問題,相對pyqtsignal而言,優點是不用去注意QObejct的初始化順序,以及pythonic的參數輸入。

from collections import dequefrom threading import Lockclass MySignal(object): def __init__(self): self.collection = deque() self.lock = Lock() def connect(self, fun): if fun not in self.collection: self.collection.append(fun) def emit(self, *args, **kwargs): self.lock.acquire() for fun in set(self.collection): fun(*args, **kwargs) self.lock.release()

推薦閱讀:

Design Pattern 劃分方式是對設計的邏輯思考
編寫可維護代碼之「中間件模式」
一種Python全局配置規範以及其魔改
【遊戲設計模式】之三 狀態模式、有限狀態機 & Unity版本實現
「小白DAY4」這樣你就懂了,談CSS設計模式

TAG:Python | PyQt | 设计模式 |