Qt 框架的信号,它支持的类型范围为:
| 类别 | 支持的类型 | 示例 |
|---|---|---|
| 基础 Python 类型 | int, float, str, bool, list, dict, tuple | Signal(int, str, list) |
| Qt 专用类型 | QWidget, QPoint, QColor, QSize 等 | Signal(QWidget, QPoint) |
| 自定义类型 | 自定义 Python 类(需注册) | Signal(MyCustomClass) |
| 无类型 / 任意类型 | object | Signal(object) |
在实际工程中,经常会用到几个不同类型的变量组成的"复合"信号,比如视觉项目中,相机线程会将相机采集到的一幅图像的像素数据和采集的时间戳组合起来发给AI线程,AI线程对图像进行识别后再把标注了目标框的像素数据与识别结果以及时间戳组合起来发给UI线程以及后台保存线程,那么在定义信号的时候,当然可以使用列出信号元素的所有类型参数的方法,比如:
python
signal = Signal(str, str, float, QPixmap) # 时间戳,识别内容,置信度,显示像素
但是这种方法在使用的过程中并不直观,如上例中的2个str,它分别代表的含义在调用时并不明示。如果将信号传递的内容打包定义成一个类去创建、编辑和传递,在工程使用和移植代码的时候就很方便。
下面就列出几种定义信号传递的内容的常用方法:
一、将传递的内容定义为Qt的QObject基类
QObject:这是 Qt 中所有支持信号槽、属性系统、对象树管理的类的基类。- 意味着该类可以使用 Qt 的核心特性(如属性通知、父子对象管理等),而非普通 Python 类。
python
import sys
from PySide6.QtCore import QObject, Signal
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout
# 定义一个类,用来封装猫咪信息并作为信号发射
class CatSignal(QObject):
def __init__(self, name: str, age: int, pixmap: QPixmap, parent=None):
super().__init__(parent)
self.name = name
self.age = age
self.pixmap = pixmap
# 可选:添加数据验证,避免非法值
if not isinstance(name, str) or len(name) == 0:
raise ValueError("猫咪名称必须是非空字符串")
if not isinstance(age, int) or age < 0:
raise ValueError("猫咪年龄必须是非负整数")
if not isinstance(pixmap, QPixmap) or pixmap.isNull():
raise ValueError("图片必须是有效的 QPixmap")
class MyWidget(QWidget):
image_signal = Signal(CatSignal)
def __init__(self):
super().__init__()
self.setWindowTitle("猫咪信息")
layout = QVBoxLayout(self)
self.label1 = QLabel(self)
self.image_signal.connect(self.on_image_signal)
self.label1.setScaledContents(True)
self.label2 = QLabel(self)
layout.addWidget(self.label1)
layout.addWidget(self.label2)
def on_image_signal(self, cat):
self.label1.setPixmap(cat.pixmap)
self.label2.setText(f"猫咪名字:{cat.name} , 猫咪年龄:{cat.age}")
if __name__ == '__main__':
app = QApplication(sys.argv)
form = MyWidget()
form.show()
cat1 = CatSignal("三毛", 2, QPixmap("cat1.jpg")) # 创建信号
form.image_signal.emit(cat1) # 发射信号
sys.exit(app.exec())

二、将传递的内容定义为QGraphicsObject
如果在视图和场景体系中应用:
python
import sys
from PySide6.QtCore import QObject, Signal, QRectF, Qt
from PySide6.QtGui import QPixmap, QPainter
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout, QGraphicsObject, QGraphicsView, \
QGraphicsScene, QGraphicsPixmapItem
class CatSignal(QGraphicsObject):
def __init__(self, name: str, age: int, pixmap: QPixmap, parent=None):
super().__init__(parent)
self.name = name
self.age = age
self.pixmap = pixmap
class MyWidget(QWidget):
image_signal = Signal(CatSignal)
def __init__(self):
super().__init__()
self.setWindowTitle("猫咪信息")
self.layout = QVBoxLayout(self)
self.view = QGraphicsView(self)
self.scene = QGraphicsScene(self)
self.view.setScene(self.scene)
self.layout.addWidget(self.view)
self.label = QLabel(self)
self.label.setScaledContents(True)
self.layout.addWidget(self.label)
self.image_signal.connect(self.on_image_signal)
def on_image_signal(self, cat):
self.scene.clear()
item_graphics = QGraphicsPixmapItem(cat.pixmap)
self.scene.addItem(item_graphics)
self.label.setText(f"猫咪名字:{cat.name} , 猫咪年龄:{cat.age}")
self.layout.update()
if __name__ == '__main__':
app = QApplication(sys.argv)
form = MyWidget()
form.show()
cat1 = CatSignal("MiMi", 2, QPixmap("cat1.jpg"))
form.image_signal.emit(cat1) # 发射信号
sys.exit(app.exec())
三、将传递的内容定义为Python的object基类
- 适用场景:最简单的场景,仅需封装属性,无额外需求。
- 优势:轻量、无依赖,支持类型注解和数据验证。
- **缺点:**不具备Qt的Qobject的核心特性(如属性通知、父子对象管理等)。
python
class CatSignal(object):
def __init__(self, name: str, age: int, pixmap: QPixmap):
super().__init__()
self.name = name
self.age = age
self.pixmap = pixmap
# 可选:添加数据验证,避免非法值
if not isinstance(name, str) or len(name) == 0:
raise ValueError("猫咪名称必须是非空字符串")
if not isinstance(age, int) or age < 0:
raise ValueError("猫咪年龄必须是非负整数")
if not isinstance(pixmap, QPixmap) or pixmap.isNull():
raise ValueError("图片必须是有效的 QPixmap")
四、使用dataclasses.dataclass
- 适用场景 :需要自动生成
__init__、__repr__、__eq__等方法,简化数据类代码。 - 核心优势:减少重复代码,支持默认值、类型检查、冻结(不可修改)等特性。
python
from dataclasses import dataclass, field
@dataclass(frozen=True) # frozen=True:实例不可修改(只读数据)
class CatSignal:
name: str # 必选属性
pixmap: QPixmap = field(compare=False) # 比较时忽略 pixmap(避免大对象比较)
age: int = field(default=0) # 可选属性,默认值 0
# 可选:字段验证(通过 __post_init__ 实现)
def __post_init__(self):
if len(self.name) == 0:
raise ValueError("名称不能为空")
if self.pixmap.isNull():
raise ValueError("无效图片")
if self.age < 0:
raise ValueError("年龄不能为负")
五、使用typing.NamedTuple
- 适用场景:需要轻量级、只读的数据容器,支持 tuple 特性(可迭代、哈希)。
- 优势 :比
dataclass更轻量,天然支持解构(name, age = cat)。
python
import sys
from dataclasses import dataclass, field
from PySide6.QtCore import QObject, Signal
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout
# 定义一个类,用来封装猫咪信息并作为信号发射
from typing import NamedTuple
class CatSignal(NamedTuple):
name: str
pixmap: QPixmap
age: int = 0
class MyWidget(QWidget):
image_signal = Signal(CatSignal)
def __init__(self):
super().__init__()
self.setWindowTitle("猫咪信息")
layout = QVBoxLayout(self)
self.label1 = QLabel(self)
self.image_signal.connect(self.on_image_signal)
self.label1.setScaledContents(True)
self.label2 = QLabel(self)
layout.addWidget(self.label1)
layout.addWidget(self.label2)
def on_image_signal(self, cat):
self.label1.setPixmap(cat.pixmap)
self.label2.setText(f"猫咪名字:{cat.name} , 猫咪年龄:{cat.age}")
if __name__ == '__main__':
app = QApplication(sys.argv)
form = MyWidget()
form.show()
# 使用(实例不可修改)
cat1 = CatSignal("咪酱", QPixmap("cat1.jpg"), 3)
print(cat1.name)
name, pixmap, age = cat1 # 解构
print(name, age)
form.image_signal.emit(cat1) # 发射信号
sys.exit(app.exec())
六、数据库储存场景
-
若需将数据通过信号发射到数据库线程,存入数据库(如 SQLite、MySQL),可继承 ORM 框架的基类:
- SQLAlchemy:
declarative_base() - Django ORM:
models.Model
- SQLAlchemy:
-
示例(SQLAlchemy):
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String, Integer, LargeBinary
from PyQt5.QtGui import QPixmap
import ioBase = declarative_base()
class CatDB(Base):
tablename = "cats" # 数据库表名id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(50), nullable=False) # 名称(非空) age = Column(Integer, default=0) # 年龄 pixmap_data = Column(LargeBinary) # 图片二进制数据(QPixmap 无法直接存数据库) # 转换 QPixmap 为二进制(存库) def set_pixmap(self, pixmap: QPixmap): buffer = io.BytesIO() pixmap.save(buffer, "PNG") self.pixmap_data = buffer.getvalue() # 从二进制转换为 QPixmap(读库) def get_pixmap(self) -> QPixmap: buffer = io.BytesIO(self.pixmap_data) return QPixmap.fromImageReader(buffer)
七、对比
| 需求场景 | 推荐基类 | 核心优势 |
|---|---|---|
| Qt 图形场景(GraphicsScene) | QGraphicsObject、QObject |
可以使用 Qt 的核心特性,QGraphicsObject支持绘图、碰撞检测、动画 |
| 仅封装数据(轻量) | object |
无依赖,最简单 |
| 数据类(自动生成方法) | dataclasses.dataclass |
简化代码,支持验证、默认值 |
| 只读数据容器 | typing.NamedTuple |
轻量,支持解构、哈希 |
| 数据库存储 | SQLAlchemy Base/Django Model |
ORM 映射,直接操作数据库 |
总结
- 优先选纯 Python 基类 :若信号传递的内容仅为数据载体,
dataclass(灵活)或NamedTuple(只读)是最优选择,轻量且无 Qt 耦合; - Qt 视图场景 :继承
QAbstractItemModel子类,享受 Qt 模型视图的自动同步特性; - 图形 / 信号槽场景 :继承
QGraphicsObject或只保留QObject,利用 Qt 的图形 / 信号机制; - 数据库 / 序列化场景:使用 ORM 或序列化库的基类,简化数据持久化。
根据实际需求(是否需要 UI 绑定、数据库存储、只读特性等)选择即可,大多数场景下 dataclasses.dataclass 是兼顾简洁性和功能性的首选。