news 2025/12/29 2:13:32

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/25 11:49:47

雷科电力-REKE-WS瓦斯继电器自动测试仪

一、概念:轻瓦斯:当变压器内部发生轻微故障时,瓦斯产生的速度较缓慢,瓦斯上升至储油柜途中首先积存于瓦斯继电器的上部空间,使油面下降,浮筒随之下降而使水银接点闭合,接通报警信号。重瓦斯&…

作者头像 李华
网站建设 2025/12/26 21:14:16

C# 项目调试的时候进不去断点

场景:A项目引用了B.dll,改完B项目代码生成后(假设这里给B项目中的Test1类添加了一个字段),调试A项目的时候,报错:未在Test1中找到字段:xxx在编辑界面不报错,运行调试才报…

作者头像 李华
网站建设 2025/12/25 3:38:59

手工制造火箭的可行性

手工制造火箭的可行性分析1. 基础物理原理火箭推进依赖于牛顿第三定律,其推力$F$由喷气反作用力提供: $$ F \dot{m} v_e $$ 其中$\dot{m}$为质量流率(kg/s),$v_e$为排气速度(m/s)。实际推力需考…

作者头像 李华
网站建设 2025/12/25 1:35:03

LobeChat本地安装详细教程

LobeChat 本地部署实战指南:从零搭建私有 AI 聊天平台 在大模型应用日益普及的今天,越来越多开发者和企业开始关注一个问题:如何在保障数据隐私的前提下,构建一个功能强大又易于使用的 AI 对话系统?市面上虽然有不少现…

作者头像 李华
网站建设 2025/12/24 19:00:27

别再重复造轮子!ZGI 资源广场让企业 AI 应用开发效率翻倍

“这个月要同时推进三个 AI 项目:HR 的员工入职问答助手、电商部门的售后智能客服、生产部的设备故障诊断系统,就我们两个人手,怎么可能按时完成?” 这是很多中小型企业开发者经常面临的困境。更让人无奈的是,每个项目…

作者头像 李华
网站建设 2025/12/24 19:44:28

LangGraph工作流转换为LangFlow可视化实践

LangGraph工作流转换为LangFlow可视化实践 在构建AI驱动的应用时,我们常常面临一个两难:一方面希望借助代码实现灵活、可追踪的复杂逻辑(如使用LangGraph定义状态机),另一方面又渴望通过拖拽式界面快速验证想法、降低…

作者头像 李华