PyQt信号槽的Python实现
(https://www.zhihu.com/people/li-ding-ke)
最近看书的时候看到了观察者模式的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-8
from PyQt4.QtCore import pyqtSignal, QObject
class 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', msg
if __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-8
class 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', msg
if __name__ == "__main__":
send = MyTypeSignal()
slot = MyTypeSlot()
send.sendmsg.connect(slot.get)#链接信号槽
send.run()
#>>My slot get msg send
MySignal类中定义了一个列表保存槽函数,在connect方法中绑定槽函数,在emit中遍历执行,一个简版的信号槽就实现了。这个信号槽支持python风格的参数,也不用为了多重继承而烦心。
当然,这里并没有线程安全和disconnect方面的东西,需要时再添加也容易。写这篇文章的目的也是想请教大家帮忙看看,有没有什么问题是我没考虑到的,以及我理解不到位的。
下面是我的知乎专栏,欢迎关注:
——————————————2017年5月16日————————————————————
更新了线程安全的版本,做过过线程安全的测试,在最近的使用中没什么问题,相对pyqtsignal而言,优点是不用去注意QObejct的初始化顺序,以及pythonic的参数输入。
from collections import deque
from threading import Lock
class 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()
网友评论