news 2025/12/27 0:17:22

PySide6从0开始学习的笔记(五) 信号与槽

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PySide6从0开始学习的笔记(五) 信号与槽

信号与槽是 Qt 框架的核心机制,用于实现对象间的通信,是解耦界面组件交互、异步处理事件、实现前后端分离的关键。


一、核心概念

1. 信号(Signal)

  • 定义:对象在特定事件触发时发出的 “通知”(比如按钮被点击、输入框文本变化、自定义事件发生)。
  • 特性
    • 信号是QObject类的属性(QObject是pyside6的一个基础类),信号本身是由pyside6的 QtCore.Signal 类定义。信号的定义、存储、触发都依赖QObject类,脱离类的信号是无法定义和使用的,这点很重要。另外初学者常见的坑是将信号定义成了变量,同样造成信号无法使用。
    • 信号本身不包含逻辑,仅负责 “发送通知”。
    • 一个信号可以连接多个槽,一个槽也可以接收多个信号。

2. 槽(Slot)

  • 定义:接收信号并执行具体逻辑的函数 / 方法(可以是普通函数、类方法、lambda 表达式)。
  • 特性
    • 槽可以有参数(需与信号的参数类型匹配),也可以无参数。
    • 槽的执行时机由信号触发,而非主动调用(也可主动调用,没有别的影响)。
    • 通常将声明为@slot(),方便识别和特殊调用。

3. 核心逻辑

  • 信号与槽通过 connect() 方法建立关联,当信号发出时,所有连接的槽会自动执行:

信号.emit(参数) → 触发所有连接的槽 → 槽函数执行


二、基础使用方法

1. 内置信号与槽(最常用)

Qt 内置组件(如 QPushButton、QLineEdit、QSlider)已预定义大量信号,直接连接即可使用。

示例 1:按钮点击触发槽函数
import sys from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout class MyWindow(QWidget): def __init__(self): super().__init__() self.init_ui() self.num = 0 def init_ui(self): # 设置窗口布局 layout = QVBoxLayout() self.btn = QPushButton("点击我") layout.addWidget(self.btn) self.setLayout(layout) # 核心:连接信号与槽 self.btn.clicked.connect(self.on_button_click) # 当按钮被点击,按钮的内置clicked信号就会被发射,从而调用执行它绑定的自定义槽函数 on_button_click # 自定义槽函数 def on_button_click(self): print("按钮被点击了!") self.num += 1 self.btn.setText(f"已点击{self.num}次") if __name__ == "__main__": app = QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec())

需要注意的是:一定不要把

some_signal.connect(some_slot),

写成:

some_signal.connect(some_slot())

比如上面代码,把连接语句写成:

self.btn.clicked.connect(self.on_button_click())

就会报错。

就是说,信号与槽连接的本质是“传递槽函数引用”而不是连接执行槽函数后的返回值,这也是初学者常踩的坑。除非some_slot()的返回值是一个函数。

  • 另,如果槽函数只是执行临时的和简单的逻辑,也可以将信号连接到Lambda函数:
# 核心:连接信号与槽 self.btn.clicked.connect(lambda: self.btn.setText("已点击") )

示例 2:自定义信号(不带参数)
import sys from PySide6.QtCore import Signal from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout class MyWindow(QWidget): # 自定义信号:必须定义为类属性,可以指定参数类型(str, int 等) custom_signal = Signal() # 定义一个不带类型参数的信号 def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.btn = QPushButton("发送自定义信号") self.label = QLabel("等待信号...") layout.addWidget(self.btn) layout.addWidget(self.label) self.setLayout(layout) # 1. 连接按钮点击信号到发送自定义信号的发射函数 self.btn.clicked.connect(self.custom_signal.emit) # 2. 连接自定义信号到槽函数 self.custom_signal.connect(lambda: self.label.setText("不带参数信号")) if __name__ == "__main__": app = QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec())

关键说明:

  • clicked 是 QPushButton 的内置信号(点击事件)。
  • connect() 方法将信号与槽函数绑定。
  • 槽函数 on_button_click 无参数,匹配clicked 信号无参数版本(clicked 信号有两个版本,一个带有clicked(bool)参数,另一个没有参数)。
示例 3:自定义信号(带参数)
import sys from PySide6.QtCore import Signal from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout class MyWindow(QWidget): # 自定义信号:必须定义为类属性,指定参数类型(str, int 等) # 定义一个带 str 类型参数的信号 custom_signal = Signal(str) def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.btn = QPushButton("发送自定义信号") self.label = QLabel("等待信号...") layout.addWidget(self.btn) layout.addWidget(self.label) self.setLayout(layout) # 1. 连接自定义信号到槽函数 self.custom_signal.connect(self.on_custom_signal) # 2. 连接按钮点击信号到发送自定义信号的函数 self.btn.clicked.connect(self.send_custom_signal) def send_custom_signal(self): # 发送自定义信号,携带参数 self.custom_signal.emit("自定义信号触发成功!") def on_custom_signal(self, msg): # 槽函数接收信号参数并处理 self.label.setText(msg) if __name__ == "__main__": app = QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec())

自定义信号的规则:

  • 信号必须定义在继承 QObject 的类中(PySide6 所有界面组件都继承 QObject)。
  • 定义格式:信号名 = Signal(参数类型1, 参数类型2, ...),支持的类型包括 int、str、float、bool、自定义类等。
  • 发送信号:self.信号名.emit(参数1, 参数2, ...),参数数量 / 类型必须与定义一致。
  • 如果信号有多个参数,槽函数在接收时可以选择接收一部分参数,或者是全部接收。

例子:

import sys from PySide6.QtCore import Signal from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout class MyWindow(QWidget): # 自定义信号:必须定义为类属性,指定参数类型(str, int 等) # 定义一个带 str 类型参数的信号 custom_signal = Signal(int, int, str) def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.btn = QPushButton("发送自定义信号") layout.addWidget(self.btn) self.setLayout(layout) # 1. 连接自定义信号到槽函数 self.custom_signal.connect(self.on_custom_signal_1) # 连接自定义信号到槽函数,一个信号可以连接到多个槽函数 self.custom_signal.connect(self.on_custom_signal_2) self.custom_signal.connect(self.on_custom_signal_all) self.custom_signal.connect(self.on_custom_signal_none) # 2. 连接按钮点击信号到发送自定义信号的函数 self.btn.clicked.connect(self.send_custom_signal) def send_custom_signal(self): # 发送自定义信号,携带参数 self.custom_signal.emit(1, 2, "3") def on_custom_signal_1(self, one): # 只接收信号的第一个参数 # 槽函数接收信号参数并处理 print(f"接收了信号参数:{one}") def on_custom_signal_2(self, one, two): # 只接收信号的第一个和第二个参数 # 槽函数接收信号参数并处理 print(f"接收了信号参数:{one, two}") def on_custom_signal_all(self, *all): # 接收信号的所有参数 # 槽函数接收信号参数并处理 print(f"接收了信号参数:{all}") def on_custom_signal_none(self): # 不接收信号的任何参数 # 槽函数接收信号参数并处理 print("未接收任何参数") if __name__ == "__main__": app = QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec())

运行结果:

接收了信号参数:1 接收了信号参数:(1, 2) 接收了信号参数:(1, 2, '3') 未接收任何参数

小结一下:

信号在发射时参数的数量和类型必须与定义一致,槽函数在接收时可以选择不接收任何参数或接收前几个参数或接收所有参数。


三、信号与槽的高级特性

1. 多信号连接同一槽
import sys from PySide6.QtCore import Signal from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout class MyWindow(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.btn1 = QPushButton("按钮1") self.btn1.setObjectName("btn1") self.btn2 = QPushButton("按钮2") self.btn2.setObjectName("btn2") self.label = QLabel("") layout.addWidget(self.btn1) layout.addWidget(self.btn2) layout.addWidget(self.label) self.setLayout(layout) # 1. 连接自定义信号到槽函数 self.btn1.clicked.connect(self.btns_clicked) self.btn2.clicked.connect(self.btns_clicked) def btns_clicked(self): # 接收信号的所有参数 # 槽函数接收信号参数并处理 self.label.setText(f"接收了{self.sender().text()}发送的信号") print(self.sender().objectName()) if __name__ == "__main__": app = QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec())

  • 知识点:槽函数在接收信号的时候是可以通过sender()函数获知发送者的身份的。
2. 一个信号连接多个槽
import sys from PySide6.QtCore import Signal from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout class MyWindow(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.btn1 = QPushButton("按钮1") self.label = QLabel("") layout.addWidget(self.btn1) layout.addWidget(self.label) self.setLayout(layout) # 1. 连接自定义信号到槽函数 self.btn1.clicked.connect(self.slot1) self.btn1.clicked.connect(self.slot2) def slot1(self): # 槽函数接收信号参数并处理 self.label.setText("按钮被点击1") def slot2(self): print("按钮被点击2") if __name__ == "__main__": app = QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec())

3. 跨线程信号与槽

PySide6 中主线程和子线程,通过信号与槽通信:

import sys import time from PySide6.QtCore import QThread, Signal from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout # 子线程类 class WorkThread(QThread): # 自定义信号:向主线程发送进度 progress_signal = Signal(int) def run(self): # 耗时操作 for i in range(1, 101): time.sleep(0.05) self.progress_signal.emit(i) # 发送进度信号 class MyWindow(QWidget): def __init__(self): super().__init__() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.btn = QPushButton("开始耗时操作") self.label = QLabel("进度:0%") layout.addWidget(self.btn) layout.addWidget(self.label) self.setLayout(layout) self.btn.clicked.connect(self.start_work) def start_work(self): # 创建子线程 self.thread = WorkThread() # 连接子线程信号到主线程槽 self.thread.progress_signal.connect(self.update_progress) # 启动线程 self.thread.start() # 禁用按钮防止重复点击 self.btn.setEnabled(False) def update_progress(self, progress): self.label.setText(f"进度:{progress}%") if progress == 100: self.btn.setEnabled(True) self.label.setText("操作完成!") if __name__ == "__main__": app = QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec())

4. 断开连接

使用 disconnect() 解除信号与槽的关联,适用于动态控制的场景:

# 断开单个连接 self.btn.clicked.disconnect(self.on_click) # 断开所有连接 self.btn.clicked.disconnect()

四、常见问题与注意事项

1. 信号定义位置错误

  • 错误:将信号定义在 __init__ 方法内定义成了变量(信号必须是类属性,需要在 __init__之前定义)。
  • 正确:直接定义在类体中(如 class MyWindow(QWidget): custom_signal = Signal(str))。

2. 槽函数参数不匹配

  • 报错:TypeError: slot_function() takes 1 positional argument but 2 were given。
  • 解决:确保槽函数参数数量 ≤ 信号参数数量,且类型匹配。

3. 多次连接导致槽重复执行

  • 问题:重复调用 connect() 会导致一个信号触发多次槽。
  • 解决:
    1. 连接前先 disconnect():self.signal.disconnect()(清空所有连接)。
    2. 确保只连接一次(如在 __init__ 中连接)。

4. 子线程对象被提前销毁

  • 问题:子线程对象作为局部变量被销毁,导致信号无法发送。
  • 解决:将子线程对象设为实例属性(如 self.thread = WorkThread())。

5. 循环引用导致内存泄漏

  • 问题:信号与槽的循环引用(如 A 连接 B 的信号,B 又连接 A 的信号)。
  • 解决:
    1. 使用 QtCore.QObject.destroyed 信号清理连接。
    2. 手动 disconnect() 不再需要的连接。

6. 使用QMetaObject.connectSlotsByName()自动连接槽函数,见:

https://blog.csdn.net/xulibo5828/article/details/155712173


总结

PySide6 信号与槽的核心价值是解耦对象通信,关键要点:

  1. 信号是 “通知”,槽是 “处理逻辑”,通过 connect() 绑定。
  2. 内置组件自带常用信号,自定义信号需继承 QObject 并通过 Signal 定义。
  3. 支持多信号连一槽、一信号连多槽,跨线程通信需通过信号(子线程不直接操作 UI)。
  4. 注意参数匹配、避免重复连接、防止对象提前销毁。

掌握信号与槽是 PySide6 开发的核心,无论是简单的按钮点击,还是复杂的异步任务处理,都依赖这一机制实现灵活的交互逻辑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/16 17:01:21

Langflow:拖拽式AI工作流颠覆编程

Langflow:拖拽式AI工作流如何重塑开发体验 你有没有试过花几个小时写完一段 LangChain 代码,结果运行时发现提示词拼错了变量?或者团队里的产品经理拿着流程图问:“这个逻辑明明很简单,为什么还要两周才能上线&#xf…

作者头像 李华
网站建设 2025/12/16 16:59:33

LLaMA-Factory:高效微调百款大模型的工具

LLaMA-Factory:让百款大模型微调变得触手可及 在当前大模型技术飞速演进的背景下,如何快速、低成本地定制专属模型,已成为研究者与开发者共同关注的核心命题。面对动辄数十GB显存、复杂依赖和陡峭学习曲线的传统微调流程,一个真正…

作者头像 李华
网站建设 2025/12/27 0:14:48

LobeChat能否用于构建AI绘画助手?多模态支持前景展望

LobeChat 能否用于构建 AI 绘画助手?多模态支持前景展望 在生成式 AI 浪潮席卷创意产业的今天,越来越多的设计师、内容创作者甚至普通用户开始期待一种更自然、更直观的人机协作方式——用对话来“指挥”AI 完成图像创作。想象这样一个场景:你…

作者头像 李华
网站建设 2025/12/16 16:59:06

PaddlePaddle高性能推理引擎Paddle Inference安装与测试

Paddle Inference:从安装到实战的高性能推理引擎深度实践 在AI模型日益复杂、部署场景愈发多样的今天,一个常见的现实是:模型训练得再好,如果推理慢、资源占用高、部署困难,依然无法真正落地。尤其是在金融交易实时风控…

作者头像 李华
网站建设 2025/12/16 16:58:41

第二章(2.5):微控制器8051的硬件结构---时钟、复位和MCU工作方式

时钟电路与时序微控制器的时钟为CPU和各个功能模块的协调工作提供同步信号和基本时序信号。时钟电路经典8051MCU必须通过外接晶振、电容,与内部时钟电路构成时钟发生器来产生MCU工作需要的信号,如下图所示。晶振频率范围一般为1.2MHz~12MHz,常…

作者头像 李华