news 2026/4/16 10:52:40

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进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/11 6:19:09

npm run dev启动GPT-SoVITS前端管理界面

npm run dev启动GPT-SoVITS前端管理界面 在语音合成技术正以前所未有的速度渗透进内容创作、无障碍服务和虚拟人交互的今天,一个令人兴奋的趋势正在浮现:仅用一分钟录音,就能克隆出高度拟真的个性化声音。这不再是科幻电影中的桥段&#xff0…

作者头像 李华
网站建设 2026/4/14 3:54:28

口碑好的污水处理厂清淤施工哪个好

口碑好的污水处理厂清淤施工:巴洛仕集团水下清淤机器人引领行业新高度在污水处理厂的运营中,清淤施工是至关重要的环节。口碑好的清淤施工不仅能确保污水处理厂的高效运行,还能延长其使用寿命。而巴洛仕集团水下清淤机器人,凭借卓…

作者头像 李华
网站建设 2026/4/5 19:55:05

Dify与Anything-LLM双平台整合:打通智能应用开发全流程

Dify与Anything-LLM双平台整合:打通智能应用开发全流程 在企业智能化转型的浪潮中,一个现实问题日益凸显:大语言模型虽然能“说人话”,却常常“不懂事”——它不了解公司内部的制度、合同模板或产品手册。而与此同时,大…

作者头像 李华
网站建设 2026/4/13 2:32:50

被“搜索”困住的我们,终于有救了?

hi兄弟们,我是麦当mdldm,一个致力于把AI说明白、让大家都能用起来的0基础AI教学博主。 第一幕:被“搜索”困住的我们,终于有救了? 兄弟们,问大家一个扎心的问题:你们平时做方案、写报告&#x…

作者头像 李华
网站建设 2026/4/13 3:46:30

机器学习高阶教程<3>统计学习理论进阶

你有没有过这样的经历:花了一周调参的模型,在训练集上准确率直奔99%,一到测试集就“翻车”到60%?对着混乱的误差曲线抓头发时,是不是忍不住想问:到底有没有一套理论,能让我们提前预判模型的泛化…

作者头像 李华
网站建设 2026/4/14 6:00:45

PaddleNLP中文处理利器:使用git从官方仓库下载并本地部署

PaddleNLP中文处理利器:使用Git从官方仓库下载并本地部署 在中文自然语言处理的实际项目中,开发者常常面临一个现实挑战:如何在保证模型性能的同时,实现对核心代码的完全掌控?尤其是在金融、政务等对数据安全和系统稳定…

作者头像 李华