不香不帅的个人主页

最后更新:2026-10-10
原创 PyQT/PySide信号和槽的深度解析
浏览: 155

一、信号和槽的概念

Qt 的信号与槽(Signals and Slots)机制是一个用于对象间通信的核心特性。这个机制使得对象能以松散耦合的方式进行通信,从而提升了代码的模块化和可维护性。

1.1信号(Signal)

当特定条件满足或事件发生时,例如UI交互中的按钮被点击、选择触发事件、用户输入事件以及后台线程执行到触发点等等,对象会发出一个信号。信号本质上是一种特殊的函数声明,它不需要具体的实现,只负责通知外部此处有相应的信号产生了,告知大家。

1.2槽(Slot)

槽是用来接收信号的函数,既可以是普通函数,也可以是 Lambda 表达式。当一个信号被发射时,与其连接的槽函数就会被自动调用,从而完成相应的处理过程。

二、定义信号和槽

2.1 自定义信号的定义

除了标准UI控件所提供的信号外,我们可以自己定义所需的自定义信号。在PyQT/PySide中,信号的定义很简单,如下所示:

#PyQT信号的定义
#无参数信号
customSignal1 = pyqtSignal()
#带一个整数参数的信号
customSignal2 = pyqtSignal(int)
#带一个字符串参数的信号
customSignal3 = pyqtSignal(str)
#带一个整数和一个字符串参数的信号
customSignal4 = pyqtSignal(int,str)
#带一个列表参数的信号
customSignal5 = pyqtSignal(list)
#带一个字典参数的信号
customSignal6 = pyqtSignal(dict)
#带一个整数或者字符串的信号
customSignal7 = pyqtSignal([int],[str])
#PySide信号的定义
#无参数信号
customSignal1 = Signal()
#带一个整数参数的信号
customSignal2 = Signal(int)
#带一个字符串参数的信号
customSignal3 = Signal(str)
#带一个整数和一个字符串参数的信号
customSignal4 = Signal(int,str)
#带一个列表参数的信号
customSignal5 = Signal(list)
#带一个字典参数的信号
customSignal6 = Signal(dict)
#带一个整数或者字符串的信号
customSignal7 = Signal([int],[str])

信号定义中,参数的格式是不限制的。参数的类型可以是常规类型,也可以是自定义对象类型。以上列举了常规的定义方法。参数的数量、类型的不同组合,您可以根据实际需要定义。


2.2 槽的定义

槽就是一个普通的函数或lambda表达式。定义一个接收信号的槽的时候,只需要按照信号的定义,定义好形参即可。例如:定义customSignal6的槽,可以这样去编写:

def customSlot6(data:dict):
  pass

当然,如果槽是类中的成员函数,还需要加上self形参。例如:

def customSlot6(self,data:dict):
  pass

三、信号和槽的连接

3.1手动连接槽

手动连接槽可以通过connect函数进行连接,例如:将上述customSignal6信号连接到customSlot6槽上,如下所示:

[对象].customSignal6.connect(customSlot6)

3.2自动连接

自动连接,可以通过装饰器@pyqtSlot(PyQT装饰器)或者@Slot(PySide装饰器)来定义。例如:

#PyQT的定义方式
@pyqtSlot(dict)
def on_[对象]_coustomSignal6(self,data:dict):
  pass
#PySide的定义方式
@Slot(dict)
def on_[对象]_coustomSignal6(self,data:dict):
  pass

注意:函数的名称必须是“on_”开始,中间是对象的objectName,后面必须是对应的信号名称。

装饰器定义好以后还不够。要自动连接还必须调用:MetaObject.connectSlotsByName函数进行统一连接。connectSlotsByName函数传递的是当前需要绑定槽所在的对象。例如下面的例子:

class EmitObject(QObject):
  signal1 = pyqtSignal(int)
  ....
  
class SlotObject(QObject):
   
   eo:EmitObject = None
   
   def __init__(self):
     self.eo = EmitObject()
     
     MetaObject.connectSlotsByName(self)    #调用这一句,才会让对象的信号和槽进行自动连接


  @pyqtSlot(int)
  def on_eo_signal1(self,data:int):
    pass


四、信号的发射

在对象内,需要发射信号的地方,调用信号的emit来发射信号。如下例所示:

class EmitObject(QObject):
  signal1 = pyqtSignal(int)
  
  def doSomething(self):
    ....
    self.signal1.emit(10)
    ....


五、信号和回调函数的区别

既然槽是一个函数,而信号发射的时候会自动去调用这个函数。这种方式就和我们常用的回调函数很类似,那他们的区别在哪里呢。

比对项目回调函数信号/槽
调用方式直接调用形参名称并传入参数使用信号的emit函数调用
触发数量调用时只会调用单个回调函数的内容通过绑定多个槽,一次emit触发多个槽执行
同步/异步
同步调用,阻塞调用位置知道回调函数执行完成不会阻塞emit调用位置,属于异步操作
返回值
因为时同步调用,可以获取回调的返回值不能够通过emit调用获取槽函数的返回值。
响应时间同步实时相应异步延迟不确定性

上述表格中,说到信号通过emit无法获取到返回值,那如何才能获取返回值呢?以下方法可以解决这个问题。

5.1 获取槽的返回值

from PySide2.QtCore import Slot,QMetaObject,QObject
from dataclasses import dataclass

# 自定义类封装返回结果
@dataclass
class SlotResultObject:
    is_valid: bool
    message: str

class MainTest(QObject):
    @Slot(str, result=SlotResultObject)    #通过result定义返回内容
    def get_bool_and_str_func(self, msg):
        return SlotResult(False, "return_message")
    
if __name__ == "__main__":
    main_test = MainTest()
    result = QMetaObject.invokeMethod(        #此处不是通过emit,而是通过invokeMethod调用
        main_test, 
        "get_bool_and_str_func", 
        QtCore.Qt.DirectConnection, 
        QtCore.Q_RETURN_ARG(SlotResultObject), 
        QtCore.Q_ARG(str, "start2")
    )
    print(result.is_valid, result.message)

当然,这种invokeMethod调用方式有些违背的信号和槽的逻辑。所以在此不建议大家使用。再非常特殊的情况下,可以参考以上的方式。


提醒:原创不易,如果您需要转载本文。为表对本人的尊重烦请标注出本文的出处。内容包括:作者和本文链接。可直接复制以下内容:

原文作者:<a href='http://www.hn-lxm.com' target="_blank">不香不帅</a>

原文链接: <a href='http://www.hn-lxm.com/article/V2lsbGlhbTQ1TGVl.html' title='PyQT/PySide信号和槽的深度解析' target="_blank">PyQT/PySide信号和槽的深度解析</a>

评论