7人参与 • 2026-01-31 • Python
当你掌握了python基础语法后,自然会想要创造一些有界面的实用工具。这时候,gui(图形用户界面)开发就成为了必经之路。在众多python gui框架中,pyqt5以其强大的功能和优雅的设计脱颖而出。
pyqt5是qt框架的python绑定,qt本身是一个久经考验的跨平台c++应用程序框架。通过pyqt5,我们可以用python语言享受到qt的所有功能,而无需与c++的复杂性打交道。
pyqt5的核心优势
适合的应用场景
在开始之前,你可能会遇到几个相似的名字,这里简单澄清一下:
pyqt5 vs pyside2:
两者都是qt的python绑定,功能几乎完全相同。主要区别在于:
pyqt6的登场:
pyqt6是pyqt5的下一代版本,主要变化包括:
为什么本文选择pyqt5?
对于初学者,pyqt5有更丰富的教程资源、更稳定的环境,而且目前大多数项目仍在使用pyqt5。掌握了pyqt5,迁移到pyqt6或pyside6也会很容易。
pip install pyqt5
uv add pyqt5==5.15.11 pyqt5-qt5==5.15.2
这里限定版本是因为pyqt5的最新版本不适配于windows uv库,如果是其他设备可能不需要限定版本。
安装完成后,可以运行以下代码验证:
import sys
from pyqt5.qtwidgets import qapplication, qlabel
app = qapplication(sys.argv)
label = qlabel("hello pyqt5!")
label.setminimumsize(400, 50)
label.show()
app.exec_()
运行效果:

(由于电脑分辨率不同,具体的大小效果可能不同)
我运行完代码之后会打印这个信息:
can't find filter element
can't find filter element
不知道是为什么,但是好像也不影响gui应用的展示,我就先不管了……
让我们从一个最简单的完整示例开始:
import sys
from pyqt5.qtwidgets import qapplication, qwidget
class mainwindow(qwidget):
def __init__(self):
super().__init__()
self.initui()
def initui(self):
# 设置窗口位置和大小
self.setgeometry(300, 300, 750, 500)
# 设置窗口标题
self.setwindowtitle('我的第一个pyqt5应用')
# 显示窗口
self.show()
if __name__ == '__main__':
# 创建应用实例
app = qapplication(sys.argv)
# 创建主窗口
window = mainwindow()
# 进入主循环
sys.exit(app.exec_())
运行效果:

代码解析:
import sys:提供对系统相关功能的访问qapplication:每个pyqt5应用都需要一个qapplication实例qwidget:所有用户界面对象的基类self.setgeometry(x, y, width, height):设置窗口位置和大小app.exec_():启动应用的事件循环qapplication是pyqt5应用的"大脑"和"心脏",负责:
重要规则:一个应用只能有一个qapplication实例!
app = qapplication(sys.argv) # sys.argv用于接收命令行参数
事件循环:gui程序的"心脏跳动"
当我们运行一个pyqt5程序时,到底发生了什么?让我用一个比喻来解释:
想象一下,你的gui程序就像一个餐厅:
为什么需要事件循环?
# 启动事件循环 app.exec_()
app.exec_() 启动了一个无限循环,这个循环不断地做三件事:
这个循环会一直运行,直到你关闭所有窗口或调用app.quit()。
sys.exit(app.exec_())到底是什么意思?
这是一个初学者常见的困惑点。让我们分解来看:
sys.exit(app.exec_())
分解理解:
app.exec_():
sys.exit():
为什么要这样写?
# 不推荐的写法 app.exec_() # 程序会在这里卡住,但退出时可能不会清理资源 # 推荐的写法 sys.exit(app.exec_()) # 确保程序正确退出,并返回状态码
工作原理示意图:
开始
↓
创建 qapplication
↓
创建窗口和控件
↓
sys.exit(app.exec_())
├── 启动事件循环(app.exec_())
│ ├── 监听用户操作
│ ├── 处理事件
│ └── 等待下一个事件...
│
└── 事件循环结束时返回状态码
↓
sys.exit(状态码) 退出程序
完整示例:理解程序执行流程
import sys
from pyqt5.qtwidgets import qapplication, qwidget, qpushbutton
class myapp(qwidget):
def __init__(self):
super().__init__()
self.initui()
def initui(self):
self.setwindowtitle('qapplication示例')
self.setgeometry(300, 300, 300, 200)
btn = qpushbutton('退出程序', self)
btn.clicked.connect(self.close) # 点击按钮关闭窗口
btn.move(100, 80)
self.show()
if __name__ == '__main__':
print("1. 程序开始")
app = qapplication(sys.argv)
print("2. qapplication实例已创建")
window = myapp()
print("3. 窗口已创建并显示")
print("4. 进入事件循环...")
exit_code = app.exec_() # 在这里程序会"阻塞",等待事件
print(f"5. 事件循环结束,退出码: {exit_code}")
sys.exit(exit_code)
运行这个程序,你会看到:
常见问题解答
q: 为什么要用sys.exit()包装app.exec_()?
a: 为了确保程序正确退出,并返回合适的退出状态码给操作系统。
q: 可以不用sys.exit()吗?
a: 在简单程序中可能可以,但在复杂程序中,不使用sys.exit()可能导致资源未正确释放。
q: 什么时候事件循环会结束?
a: 当调用app.quit()或关闭所有窗口时,事件循环会结束。
q: 事件循环期间,我的代码还能运行吗?
a: 不能直接运行。所有代码都必须在事件处理函数中执行。如果需要执行长时间任务,应该使用多线程。
记住这个模式
几乎所有pyqt5程序都遵循这个模式:
import sys
from pyqt5.qtwidgets import qapplication, qwidget
def main():
app = qapplication(sys.argv) # 1. 创建应用
window = qwidget() # 2. 创建窗口
window.show() # 3. 显示窗口
return app.exec_() # 4. 进入事件循环
if __name__ == '__main__':
sys.exit(main()) # 5. 确保正确退出
掌握了qapplication和事件循环的概念,你就理解了pyqt5程序运行的基础机制。这是构建更复杂应用的基石!
qwidget:所有可视化组件的基类
qmainwindow:带有菜单栏、工具栏、状态栏的主窗口
qmainwindow是qwidget的"加强版",但它们的定位和用途有很大区别。
核心关系:继承与扩展
# pyqt5中的继承关系
object
↓
qobject
↓
qwidget ← 所有可视化元素的基类
↓
qmainwindow ← 专门用于主窗口的特殊qwidget
简单来说:
qwidget:万能的基础窗口
qwidget是pyqt5中最基础的窗口类,它可以扮演两种角色:
1. 作为独立的简单窗口
from pyqt5.qtwidgets import qwidget, qlabel, qpushbutton, qvboxlayout, qapplication
import sys
class simplewindow(qwidget):
def __init__(self):
super().__init__()
self.setwindowtitle("我是一个qwidget窗口")
self.resize(300, 200)
# 创建布局和控件
layout = qvboxlayout()
label = qlabel("这是一个简单的对话框")
button = qpushbutton("确定")
layout.addwidget(label)
layout.addwidget(button)
self.setlayout(layout)
if __name__ == "__main__":
app = qapplication(sys.argv)
window = simplewindow()
window.show()
sys.exit(app.exec_())
运行效果:

2. 作为其他控件的容器
# qlabel、qpushbutton、qlineedit等都是qwidget的子类
# 它们都继承了qwidget的所有功能
label = qlabel("我是qwidget的子类")
button = qpushbutton("我也是qwidget的子类")
qwidget的特点:
qmainwindow:专业的应用程序主窗口
qmainwindow是专门为应用程序主窗口设计的,它提供了"开箱即用"的标准主窗口结构:
from pyqt5.qtwidgets import qapplication, qmainwindow, qtextedit, qaction, qlabel
import sys
class mainappwindow(qmainwindow):
def __init__(self):
super().__init__()
self.initui()
def initui(self):
# 1. 设置中央部件(必需)
text_edit = qtextedit()
self.setcentralwidget(text_edit)
# 2. 创建菜单栏(可选)
menubar = self.menubar()
file_menu = menubar.addmenu("文件(&f)")
# 向菜单添加具体的动作
open_action = qaction("打开", self)
save_action = qaction("保存", self)
exit_action = qaction("退出", self)
exit_action.triggered.connect(self.close)
file_menu.addaction(open_action)
file_menu.addaction(save_action)
file_menu.addseparator()
file_menu.addaction(exit_action)
# 3. 创建工具栏并添加工具按钮(必需,否则工具栏为空)
toolbar = self.addtoolbar("标准工具栏")
# 添加工具栏按钮(使用文本)
toolbar.addaction("新建")
toolbar.addaction("打开")
toolbar.addaction("保存")
toolbar.addseparator()
toolbar.addaction("打印")
# 4. 创建状态栏(可选)
status_bar = self.statusbar()
status_bar.showmessage("就绪", 3000) # 显示3秒
# 添加永久显示的状态栏部件
permanent_label = qlabel("永久状态信息")
status_bar.addpermanentwidget(permanent_label)
# 5. 设置窗口属性
self.setwindowtitle("文本编辑器")
self.setgeometry(100, 100, 800, 600)
self.show()
if __name__ == "__main__":
app = qapplication(sys.argv)
window = mainappwindow()
sys.exit(app.exec_())
运行效果(前3秒会显示就绪):

展开菜单”文件“,过3秒:

qmainwindow的标准结构
+---------------------------------------------------+
| 菜单栏 (menu bar) |
+---------------------------------------------------+
| 工具栏 (tool bar) |
+---------------------------------------------------+
| |
| 中央部件 (central widget) |
| |
| +-----------------------------------------+ |
| | | |
| | 你的主要内容在这里 | |
| | | |
| +-----------------------------------------+ |
| |
+---------------------------------------------------+
| 状态栏 (status bar) |
+---------------------------------------------------+
我们很容易发现qmainwindow里面又有菜单,又有工具栏,那我们很容易就会提问,不对啊,现在的软件不是要么只有菜单(如vscode),要么只有标签形式切换的工具栏(如word)吗?
这是一个相当复杂的问题,首先word老版(1984-2006)其实真的是又有菜单栏又有工具栏的:
┌─────────────────────────────────────┐
│ 文件(f) 编辑(e) 视图(v) 帮助(h) ← 固定菜单栏
├─────────────────────────────────────┤
│ [📄] [📂] [💾] [🖨️] [b] [i] [u] ← 浮动工具栏
└─────────────────────────────────────┘
特点:
但2007年后改为了采用这样的ribbon界面:
┌─────────────────────────────────────┐
│ 🏠 插入 设计 布局 引用 邮件 审阅 视图 ← 情境化标签页
├─────────────────────────────────────┤
│ 当前任务相关功能分组展示 ← 自适应功能区
│ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ 剪贴板 │ │ 字体 │ │ 段落 │
│ │ │ │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘
└─────────────────────────────────────┘
核心创新:
而vscode则采用的是侧边活动栏:
# vs code的界面结构
┌─────────────────────────────────────────────────────┐
│ file edit view go run terminal help │ ← 顶部菜单栏(简洁)
├─────────────────────────────────────────────────────┤
│ 🏠 🔍 💾 🐙 ⏹️ │ ← 活动栏(侧边图标栏)
│ │
│ 侧边面板区域 │
│ (资源管理器、搜索、git等) │
│ │
├─────────────────────────────────────────────────────┤
│ [main.py] │ ← 编辑器标签页
│ │
│ def main(): │ ← 主编辑区域
│ print("hello world") │
│ │
├─────────────────────────────────────────────────────┤
│ python 3.12.4 • utf-8 • lf • 2 spaces │ ← 状态栏(多信息显示)
└─────────────────────────────────────────────────────┘
关键区别对比表
| 特性 | qwidget | qmainwindow |
|---|---|---|
| 定位 | 基础窗口/控件 | 应用程序主窗口 |
| 内存占用 | 较小 | 较大(包含更多组件) |
| 内置结构 | 无 | 有标准菜单栏、工具栏、状态栏区域 |
| 布局管理 | 需要手动设置布局 | 中央部件区域可设置布局 |
| 灵活性 | 高,完全自定义 | 有一定结构限制 |
| 典型用途 | 对话框、弹窗、简单窗口 | 软件主窗口、复杂应用 |
| 是否能使用布局管理器 | 可以,直接设置 | 只能对中央部件使用布局 |
| 是否能有多个实例 | 可以 | 通常只有一个 |
关键注意事项
1. qmainwindow不能直接设置布局!
(布局的介绍见本文第6节)
# ❌ 错误做法
class wrongwindow(qmainwindow):
def __init__(self):
super().__init__()
layout = qvboxlayout()
layout.addwidget(qlabel("测试"))
self.setlayout(layout) # 这不会工作!
# ✅ 正确做法
class correctwindow(qmainwindow):
def __init__(self):
super().__init__()
# 创建一个qwidget作为中央部件的容器
central_widget = qwidget()
self.setcentralwidget(central_widget)
# 在容器上设置布局
layout = qvboxlayout()
layout.addwidget(qlabel("测试"))
central_widget.setlayout(layout) # 在中央部件上设置布局
2. 实际应用中的选择策略
# 场景1:需要标准菜单栏/工具栏的软件 → 选择qmainwindow
class texteditor(qmainwindow):
"""文本编辑器,需要菜单栏保存文件"""
pass
# 场景2:简单的配置对话框 → 选择qwidget
class settingsdialog(qwidget):
"""设置对话框,不需要复杂的菜单结构"""
pass
# 场景3:复杂应用的子窗口 → 选择qwidget
class previewwindow(qwidget):
"""预览窗口,作为主窗口的子窗口"""
pass
3. 混合使用示例
from pyqt5.qtwidgets import qapplication, qmainwindow, qtextedit, qwidget
import sys
class mainapp(qmainwindow):
def __init__(self):
super().__init__()
# 主窗口使用qmainwindow
self.setwindowtitle("主应用程序")
self.setcentralwidget(qtextedit())
# 但点击按钮可以弹出qwidget对话框
self.settings_dialog = settingsdialog(self)
def show_settings(self):
self.settings_dialog.exec_() # 显示模态对话框
class settingsdialog(qwidget):
def __init__(self, parent=none):
super().__init__(parent)
self.setwindowtitle("设置")
self.resize(300, 200)
# 使用qwidget作为对话框
if __name__ == "__main__":
app = qapplication(sys.argv)
window = mainapp()
window.show()
sys.exit(app.exec_())
生成效果(我手动拖拽了一下界面大小):

实践建议
什么时候用qwidget?
什么时候用qmainwindow?
一个有用的技巧:从简单开始
# 初期:从qwidget开始,快速原型
class simpleapp(qwidget):
def __init__(self):
super().__init__()
# 简单布局和功能
# 后期:需要更多功能时,轻松迁移到qmainwindow
class enhancedapp(qmainwindow):
def __init__(self):
super().__init__()
# 将原来的qwidget内容设为中央部件
old_widget = simpleapp()
self.setcentralwidget(old_widget)
# 添加菜单栏、工具栏等
总结
qwidget和qmainwindow不是"基础版"和"高级版"的关系,而是不同用途的工具:
记住这个简单的选择原则:
理解它们的区别后,你就能根据实际需求做出合适的选择,写出更专业、更高效的pyqt5代码!
这是qt最强大的特性之一!想象一下:
工作原理:事件发生 → 发出信号 → 连接到槽 → 执行函数
import sys
from pyqt5.qtwidgets import (
qapplication,
qwidget,
qvboxlayout,
qlabel,
qpushbutton,
qtextedit,
)
from pyqt5.qtcore import pyqtsignal
class custombutton(qpushbutton):
"""
自定义按钮类
演示如何创建和使用自定义信号
"""
# 自定义信号 - 可以发送一个字符串
message_signal = pyqtsignal(str)
# 另一个自定义信号 - 可以发送两个整数
number_signal = pyqtsignal(int, int)
def __init__(self, text, parent=none):
super().__init__(text, parent)
self.click_count = 0
def mousepressevent(self, event):
"""
重写鼠标按下事件
每次点击都会发出两个信号
"""
self.click_count += 1
# 发出第一个信号 - 带字符串消息
self.message_signal.emit(f"第{self.click_count}次点击!")
# 发出第二个信号 - 带两个数字
x, y = event.x(), event.y()
self.number_signal.emit(x, y)
# 重要:调用父类方法确保正常行为
super().mousepressevent(event)
class exampleapp(qwidget):
def __init__(self):
super().__init__()
self.setup_ui()
def setup_ui(self):
self.setwindowtitle("自定义信号详细示例")
self.resize(500, 400)
# 创建布局
layout = qvboxlayout()
# 1. 信息显示区域
self.info_label = qlabel("点击下面的按钮查看效果", self)
layout.addwidget(self.info_label)
# 2. 文本显示区域(用于显示详细信息)
self.text_display = qtextedit(self)
self.text_display.setreadonly(true)
layout.addwidget(self.text_display)
# 3. 创建自定义按钮
self.custom_btn = custombutton("自定义按钮 - 点击我", self)
layout.addwidget(self.custom_btn)
# 4. 重置按钮
self.reset_btn = qpushbutton("重置计数", self)
layout.addwidget(self.reset_btn)
self.setlayout(layout)
# 连接信号
self.connect_signals()
def connect_signals(self):
"""连接所有信号到槽函数"""
# 连接自定义按钮的第一个信号
self.custom_btn.message_signal.connect(self.update_info)
# 连接自定义按钮的第二个信号
self.custom_btn.number_signal.connect(self.show_click_position)
# 连接重置按钮
self.reset_btn.clicked.connect(self.reset_counter)
def update_info(self, message):
"""更新信息标签"""
self.info_label.settext(f"自定义信号: {message}")
self.text_display.append(f"收到消息: {message}")
def show_click_position(self, x, y):
"""显示点击位置"""
self.text_display.append(f"点击位置: x={x}, y={y}")
def reset_counter(self):
"""重置计数器"""
self.custom_btn.click_count = 0
self.info_label.settext("计数器已重置")
self.text_display.append("--- 计数器重置 ---")
# 运行示例
if __name__ == "__main__":
app = qapplication(sys.argv)
window = exampleapp()
window.show()
sys.exit(app.exec_())
首先设置带信号(代码里设置了两种)的按钮,然后将按钮放到组件上,把信号连接到槽函数上(槽函数也可以是一个lambda函数)。按钮被触发时,信号发射(emit),槽函数接受并处理信号,在代码中就将处理结果展示在界面上:


from pyqt5.qtwidgets import (
qlabel, # 标签 - 显示文本或图片
qpushbutton, # 按钮 - 点击触发动作
qlineedit, # 单行输入框
qtextedit, # 多行文本编辑
qcheckbox, # 复选框
qradiobutton, # 单选按钮
qcombobox, # 下拉框
qspinbox, # 数字输入框
qprogressbar, # 进度条
qslider, # 滑块
)
没有布局管理器的gui就像没有css的html——元素会堆叠在一起。
布局管理器其实就是把一堆空间按布局组合到一起。
qvboxlayout - 垂直排列
from pyqt5.qtwidgets import qvboxlayout, qpushbutton, qlabel
layout = qvboxlayout()
layout.addwidget(qlabel("第一行"))
layout.addwidget(qpushbutton("第二行"))
layout.addwidget(qlabel("第三行"))
self.setlayout(layout) # 应用到窗口
qhboxlayout - 水平排列
from pyqt5.qtwidgets import qhboxlayout
layout = qhboxlayout()
layout.addwidget(qpushbutton("左"))
layout.addwidget(qpushbutton("中"))
layout.addwidget(qpushbutton("右"))
布局嵌套 - 创建复杂界面
# 创建主垂直布局
main_layout = qvboxlayout()
# 创建水平布局并添加控件
top_layout = qhboxlayout()
top_layout.addwidget(qlabel("姓名:"))
top_layout.addwidget(qlineedit())
# 将水平布局添加到垂直布局
main_layout.addlayout(top_layout)
main_layout.addwidget(qpushbutton("提交"))
self.setlayout(main_layout)
# 错误写法 window = qwidget() window.show() # 正确写法 app = qapplication(sys.argv) window = qwidget() window.show() app.exec_()
在pyqt5(以及大多数gui框架)中,有一个黄金规则:所有gui操作都必须在主线程(也称为gui线程)中进行!
为什么有这个限制?
简化的解释:
pyqt5的gui组件不是"线程安全"的
想象一下两个线程同时修改同一个控件:
线程a: label.settext("hello") 线程b: label.settext("world")
↓ ↓
同时写入同一个内存区域 → 数据竞争 → 程序崩溃!
错误示例分析
错误代码:
import sys
import time
from pyqt5.qtwidgets import qapplication, qwidget, qvboxlayout, qlabel, qpushbutton
from pyqt5.qtcore import qthread, pyqtsignal
class workerthread(qthread):
"""工作线程"""
def run(self):
time.sleep(2) # 模拟耗时操作
# ❌ 危险!在子线程中直接更新gui
label.settext("处理完成!") # 可能导致崩溃
# 在主线程中创建窗口
app = qapplication(sys.argv)
window = qwidget()
label = qlabel("等待中...")
button = qpushbutton("开始任务")
layout = qvboxlayout()
layout.addwidget(label)
layout.addwidget(button)
window.setlayout(layout)
# 创建工作线程
worker = workerthread()
def start_task():
worker.start()
button.clicked.connect(start_task)
window.show()
sys.exit(app.exec_())
可能的结果:
正确解决方案:使用信号
方案1:qthread + movetothread
import sys
import time
from pyqt5.qtwidgets import qapplication, qwidget, qvboxlayout, qlabel, qpushbutton
from pyqt5.qtcore import qthread, pyqtsignal, qobject
class worker(qobject):
"""工作对象,使用信号通信"""
# 定义信号
progress_signal = pyqtsignal(int) # 传递进度百分比
result_signal = pyqtsignal(str) # 传递结果字符串
finished_signal = pyqtsignal() # 完成信号(无参数)
def do_work(self):
"""执行耗时任务"""
for i in range(1, 11):
time.sleep(0.5) # 模拟耗时操作
self.progress_signal.emit(i * 10) # 发射进度信号
self.result_signal.emit("处理完成!") # 发射结果信号
self.finished_signal.emit() # 发射完成信号
class mainwindow(qwidget):
def __init__(self):
super().__init__()
self.init_ui()
self.setup_worker()
def init_ui(self):
self.setwindowtitle("线程安全更新gui示例")
self.resize(400, 300)
layout = qvboxlayout()
self.label = qlabel("准备开始任务...")
self.progress_label = qlabel("进度: 0%")
self.button = qpushbutton("开始任务")
self.status_label = qlabel("状态: 空闲")
layout.addwidget(self.label)
layout.addwidget(self.progress_label)
layout.addwidget(self.status_label)
layout.addwidget(self.button)
self.setlayout(layout)
self.button.clicked.connect(self.start_work)
def setup_worker(self):
"""设置工作线程和信号连接"""
# 创建工作对象和线程
self.worker = worker()
self.thread = qthread()
# 将工作对象移动到线程中
self.worker.movetothread(self.thread)
# 连接信号
self.worker.progress_signal.connect(self.update_progress)
self.worker.result_signal.connect(self.update_result)
self.worker.finished_signal.connect(self.work_finished)
# 线程开始后,连接do_work方法
self.thread.started.connect(self.worker.do_work)
# 线程结束时,清理资源
self.worker.finished_signal.connect(self.thread.quit)
self.worker.finished_signal.connect(self.worker.deletelater)
self.thread.finished.connect(self.thread.deletelater)
def start_work(self):
"""开始工作"""
self.button.setenabled(false)
self.status_label.settext("状态: 处理中...")
self.thread.start()
def update_progress(self, progress):
"""更新进度(在主线程中执行)"""
self.progress_label.settext(f"进度: {progress}%")
def update_result(self, result):
"""更新结果(在主线程中执行)"""
self.label.settext(result)
def work_finished(self):
"""任务完成(在主线程中执行)"""
self.button.setenabled(true)
self.status_label.settext("状态: 完成")
if __name__ == "__main__":
app = qapplication(sys.argv)
window = mainwindow()
window.show()
sys.exit(app.exec_())
方案2:qrunnable
需要管理生命周期:
import sys
import time
from pyqt5.qtwidgets import qapplication, qwidget, qvboxlayout, qlabel, qpushbutton
from pyqt5.qtcore import qthreadpool, qrunnable, pyqtsignal, qobject, pyqtslot, qmutex
class workersignals(qobject):
"""定义工作线程的信号"""
finished = pyqtsignal()
progress = pyqtsignal(int)
result = pyqtsignal(str)
def __init__(self):
super().__init__()
self.is_valid = true # 添加有效性标志
def delete_later(self):
self.is_valid = false
self.deletelater()
class worker(qrunnable):
"""工作线程类"""
def __init__(self):
super().__init__()
self.signals = workersignals()
self.mutex = qmutex() # 互斥锁保护信号对象
def run(self):
"""执行耗时任务"""
for i in range(10):
time.sleep(0.3)
progress = (i + 1) * 10
# 使用互斥锁保护信号对象
self.mutex.lock()
try:
if (
self.signals
and hasattr(self.signals, "is_valid")
and self.signals.is_valid
):
self.signals.progress.emit(progress)
except runtimeerror:
# 信号对象已被删除
pass
finally:
self.mutex.unlock()
self.mutex.lock()
try:
if (
self.signals
and hasattr(self.signals, "is_valid")
and self.signals.is_valid
):
self.signals.result.emit("任务完成!")
self.signals.finished.emit()
except runtimeerror:
pass
finally:
self.mutex.unlock()
class simplethreadexample(qwidget):
def __init__(self):
super().__init__()
self.init_ui()
self.threadpool = qthreadpool()
self.workers = [] # 保持对worker的引用
def init_ui(self):
self.setwindowtitle("简单多线程示例")
self.resize(300, 200)
layout = qvboxlayout()
self.label = qlabel("点击按钮开始任务")
self.button = qpushbutton("开始耗时任务")
layout.addwidget(self.label)
layout.addwidget(self.button)
self.setlayout(layout)
self.button.clicked.connect(self.start_task)
def start_task(self):
"""启动工作线程"""
# 禁用按钮,防止重复点击
self.button.setenabled(false)
# 创建工作对象
worker = worker()
self.workers.append(worker) # 保持引用
# 连接信号
worker.signals.progress.connect(self.on_progress)
worker.signals.result.connect(self.on_result)
worker.signals.finished.connect(lambda: self.on_finished(worker))
# 在线程池中执行
self.threadpool.start(worker)
def on_progress(self, progress):
"""更新进度(自动在主线程中执行)"""
self.label.settext(f"处理中... {progress}%")
def on_result(self, result):
"""显示结果(自动在主线程中执行)"""
self.label.settext(result)
def on_finished(self, worker):
"""任务完成(自动在主线程中执行)"""
self.button.setenabled(true)
# 清理worker
if worker in self.workers:
if worker.signals:
worker.signals.delete_later()
self.workers.remove(worker)
if __name__ == "__main__":
app = qapplication(sys.argv)
window = simplethreadexample()
window.show()
sys.exit(app.exec_())
为什么信号能安全地更新gui?
信号与槽的线程安全机制:
pyqt5的内部机制:
当信号从子线程发射时,pyqt5会自动:
所以:
worker.signals.result.emit("数据") # 子线程中发射信号
↓
pyqt5内部:跨线程传递信号
↓
label.settext("数据") # 在主线程中执行槽函数
其他安全更新gui的方法
1. 使用qtimer在主线程中轮询
from pyqt5.qtcore import qtimer
class safeupdateexample:
def __init__(self):
self.results_queue = [] # 线程安全的数据结构
# 定时器在主线程中运行
self.timer = qtimer()
self.timer.timeout.connect(self.check_results)
self.timer.start(100) # 每100毫秒检查一次
def check_results(self):
"""在主线程中检查并更新gui"""
if self.results_queue:
result = self.results_queue.pop(0)
label.settext(result)
2. 使用qmetaobject.invokemethod
from pyqt5.qtcore import qmetaobject, qt, pyqtslot
class workerthread(qthread):
result_ready = pyqtsignal(str)
def run(self):
# 耗时任务
result = "处理完成"
# 安全地调用主线程的方法
qmetaobject.invokemethod(
main_window, # 目标对象
"update_label", # 方法名
qt.queuedconnection, # 异步连接
result # 参数
)
class mainwindow:
@pyqtslot(str)
def update_label(self, text):
label.settext(text) # 在主线程中执行
实际应用场景
场景1:网络请求
class downloadworker(qthread):
progress = pyqtsignal(int)
finished = pyqtsignal(bytes)
error = pyqtsignal(str)
def run(self):
try:
# 下载文件(耗时操作)
for chunk in download_file():
self.progress.emit(chunk.progress)
self.finished.emit(file_data)
except exception as e:
self.error.emit(str(e)) # 发送错误信号,而不是直接弹窗
场景2:数据处理
class dataprocessorworker(qobject):
data_processed = pyqtsignal(pd.dataframe) # 发送处理后的数据
error_occurred = pyqtsignal(str)
def process_large_data(self, data):
try:
# 复杂的数据处理
result = heavy_computation(data)
self.data_processed.emit(result)
except exception as e:
self.error_occurred.emit(f"处理失败: {e}")
常见错误模式
错误1:忘记movetothread
worker = worker() thread = qthread() # ❌ 忘记移动对象到线程 thread.start() worker.do_work() # 还在主线程中执行!
错误2:直接调用gui方法
def worker_function():
# 各种计算...
window.update_ui(data) # ❌ 危险!在子线程中调用gui方法
错误3:忽略异常处理
def worker_function():
try:
# 可能失败的操作
except exception as e:
# ❌ 不要直接显示错误对话框
qmessagebox.critical(none, "错误", str(e)) # 可能崩溃!
# ✅ 应该发送信号
self.error_signal.emit(str(e))
最佳实践总结
movetothread()确保对象在正确的线程中简单记忆法则
记住这句话:“信号发射是自由的,但槽函数执行总是在主线程的怀抱中。”
这样,您就能安全地在pyqt5中使用多线程了!
# 可能的内存泄漏
def create_widget():
widget = qwidget() # 没有父对象,需要手动管理
return widget
# 更好的做法
def create_widget(parent=none):
widget = qwidget(parent) # 指定父对象,自动管理内存
return widget
通过本文,你已经掌握了pyqt5的基础知识:
pyqt5的学习曲线可能有些陡峭,但一旦掌握,你将拥有创建强大桌面应用的能力。记住,最好的学习方式就是动手实践。从今天开始,尝试用pyqt5解决你遇到的实际问题吧!
到此这篇关于python使用pyqt5打造桌面应用的入门指南的文章就介绍到这了,更多相关python pyqt5桌面应用开发内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论