当前位置:首页>python>PyQt5入门指南:用Python打造你的第一个桌面应用

PyQt5入门指南:用Python打造你的第一个桌面应用

  • 2026-01-16 06:49:37
PyQt5入门指南:用Python打造你的第一个桌面应用

1. 引言:为什么选择PyQt5开发桌面应用?

当你掌握了Python基础语法后,自然会想要创造一些有界面的实用工具。这时候,GUI(图形用户界面)开发就成为了必经之路。在众多Python GUI框架中,PyQt5以其强大的功能和优雅的设计脱颖而出。PyQt5是Qt框架的Python绑定,Qt本身是一个久经考验的跨平台C++应用程序框架。通过PyQt5,我们可以用Python语言享受到Qt的所有功能,而无需与C++的复杂性打交道。

PyQt5的核心优势

  • • 功能全面:从简单的窗口到复杂的3D图形,应有尽有
  • • 真正的跨平台:同一份代码可在Windows、macOS、Linux上运行
  • • Pythonic的优雅:既有Qt的强大,又有Python的简洁
  • • 丰富的控件库:按钮、表格、树形视图等上百种控件
  • • 活跃的社区:遇到问题容易找到解决方案

适合的应用场景

  • • 数据分析可视化工具
  • • 日常工作效率工具
  • • 原型快速开发
  • • 中小型企业应用

2. PyQt5与PySide,以及PyQt6简介

在开始之前,你可能会遇到几个相似的名字,这里简单澄清一下:

PyQt5 vs PySide2两者都是Qt的Python绑定,功能几乎完全相同。主要区别在于:

  • • 许可证:PyQt5使用GPL/commercial,PySide2使用LGPL(对商业应用更友好)
  • • 历史:PyQt出现较早,PySide是Qt官方后来推出的
  • • API细微差别:方法名略有不同,如信号连接方式

PyQt6的登场PyQt6是PyQt5的下一代版本,主要变化包括:

  • • 默认使用Qt6库(PyQt5使用Qt5)
  • • 一些API发生了变化和优化
  • • 移除了对Python 2的支持

为什么本文选择PyQt5?对于初学者,PyQt5有更丰富的教程资源、更稳定的环境,而且目前大多数项目仍在使用PyQt5。掌握了PyQt5,迁移到PyQt6或PySide6也会很容易。

3. 快速开始:环境搭建

使用pip安装(推荐大多数用户)

pip install PyQt5

使用uv安装(新一代Python包管理工具)

uv add pyqt5==5.15.11 pyqt5-qt5==5.15.2

这里限定版本是因为pyqt5的最新版本不适配于Windows uv库,如果是其他设备可能不需要限定版本。具体原因可以参考这篇博文:Windows系统无法直接用uv安装pyqt5,但可以用uv pip安装-CSDN博客[1]

版本说明

  • • PyQt5 5.15.x 是PyQt5的最后一个系列版本
  • • 支持Python 3.5及以上版本
  • • 建议使用Python 3.8+以获得最佳体验

验证安装

安装完成后,可以运行以下代码验证:

import sysfrom PyQt5.QtWidgets import QApplication, QLabelapp = QApplication(sys.argv)label = QLabel("Hello PyQt5!")label.setMinimumSize(400, 50)label.show()app.exec_()
运行效果:
(由于电脑分辨率不同,具体的大小效果可能不同)我运行完代码之后会打印这个信息:
Can't find filter elementCan't find filter element

不知道是为什么,但是好像也不影响GUI应用的展示,我就先不管了……

4. 第一个PyQt5窗口:Hello World

让我们从一个最简单的完整示例开始:

import sysfrom PyQt5.QtWidgets import QApplication, QWidgetclass 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_())
运行效果:
代码解析
  1. 1. import sys:提供对系统相关功能的访问
  2. 2. QApplication:每个PyQt5应用都需要一个QApplication实例
  1. 3. QWidget:所有用户界面对象的基类
  2. 4. self.setGeometry(x, y, width, height):设置窗口位置和大小
  1. 5. app.exec_():启动应用的事件循环

5. 理解PyQt5的核心"积木"

5.1 QApplication:应用程序的心脏

QApplication是PyQt5应用的"大脑"和"心脏",负责:

  • • 管理应用程序的控制流:协调各个窗口和控件的工作
  • • 处理系统级事件:接收和分发鼠标点击、键盘输入、窗口重绘等事件
  • • 提供全局设置:管理应用程序的字体、样式、调色板等

重要规则:一个应用只能有一个QApplication实例!

app = QApplication(sys.argv)  # sys.argv用于接收命令行参数

事件循环:GUI程序的"心脏跳动"

当我们运行一个PyQt5程序时,到底发生了什么?让我用一个比喻来解释:想象一下,你的GUI程序就像一个餐厅:

  • • QApplication是餐厅经理
  • • 事件循环(event loop)是餐厅的服务流程
  • • 用户操作(点击、输入)就是顾客的订单为什么需要事件循环?
# 启动事件循环app.exec_()

app.exec_() 启动了一个无限循环,这个循环不断地做三件事:

  1. 1. 监听系统事件(鼠标点击、键盘输入等)
  2. 2. 将事件分发给对应的控件处理
  1. 3. 处理完事件后,等待下一个事件这个循环会一直运行,直到你关闭所有窗口或调用app.quit()

sys.exit(app.exec_()) 到底是什么意思?

这是一个初学者常见的困惑点。让我们分解来看:

sys.exit(app.exec_())

分解理解:

  1. 1. app.exec_()
    • • 启动事件循环
    • • 返回一个退出状态码(通常是0表示成功,非0表示错误)
    • • 事件循环会阻塞在这里,直到程序退出
  2. 2. sys.exit()
    • • Python标准库函数
    • • 用于退出Python程序
    • • 可以接收一个退出状态码作为参数
为什么要这样写?
# 不推荐的写法app.exec_()  # 程序会在这里卡住,但退出时可能不会清理资源# 推荐的写法sys.exit(app.exec_())  # 确保程序正确退出,并返回状态码

工作原理示意图:

开始  ↓创建 QApplication  ↓创建窗口和控件  ↓sys.exit(app.exec_())  ├── 启动事件循环(app.exec_())  │     ├── 监听用户操作  │     ├── 处理事件  │     └── 等待下一个事件...  │  └── 事件循环结束时返回状态码        ↓   sys.exit(状态码) 退出程序

完整示例:理解程序执行流程

import sysfrom PyQt5.QtWidgets import QApplication, QWidget, QPushButtonclass 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)

运行这个程序,你会看到:

  1. 1. 控制台先打印1-4步的信息
  2. 2. 然后程序进入事件循环,GUI界面出现
  1. 3. 当你关闭窗口时,事件循环结束
  2. 4. 最后打印第5步的信息,程序退出

常见问题解答

Q: 为什么要用sys.exit()包装app.exec_()A: 为了确保程序正确退出,并返回合适的退出状态码给操作系统。

Q: 可以不用sys.exit()吗?A: 在简单程序中可能可以,但在复杂程序中,不使用sys.exit()可能导致资源未正确释放。

Q: 什么时候事件循环会结束?A: 当调用app.quit()或关闭所有窗口时,事件循环会结束。

Q: 事件循环期间,我的代码还能运行吗?A: 不能直接运行。所有代码都必须在事件处理函数中执行。如果需要执行长时间任务,应该使用多线程。

记住这个模式

几乎所有PyQt5程序都遵循这个模式:

import sysfrom PyQt5.QtWidgets import QApplication, QWidgetdef 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程序运行的基础机制。这是构建更复杂应用的基石!

5.2 QWidget与QMainWindow:窗口的两大家族

QWidget:所有可视化组件的基类

  • • 按钮、标签、输入框等都是QWidget的子类
  • • 可以单独作为窗口使用

QMainWindow:带有菜单栏、工具栏、状态栏的主窗口

  • • 适用于复杂的应用程序
  • • 提供了标准的应用程序框架

QMainWindow是QWidget的"加强版",但它们的定位和用途有很大区别。

核心关系:继承与扩展

# PyQt5中的继承关系object  ↓QObject  ↓QWidget  ← 所有可视化元素的基类  ↓QMainWindow  ← 专门用于主窗口的特殊QWidget

简单来说

  • • QWidget是所有可视控件的"爷爷辈"基类
  • • QMainWindow是QWidget的一个"大儿子",专门为应用程序主窗口设计
  • • 按钮、标签、输入框等都是QWidget的其他"儿子孙子"

QWidget:万能的基础窗口

QWidget是PyQt5中最基础的窗口类,它可以扮演两种角色:

1. 作为独立的简单窗口

from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QVBoxLayout, QApplicationimport sysclass 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, QLabelimport sysclass 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界面:

┌─────────────────────────────────────┐│ 🏠 插入 设计 布局 引用 邮件 审阅 视图    ← 情境化标签页├─────────────────────────────────────┤│  当前任务相关功能分组展示            ← 自适应功能区│  ┌─────────┐ ┌─────────┐ ┌─────────┐│  │ 剪贴板  │ │ 字体    │ │ 段落    ││  │         │ │         │ │         ││  └─────────┘ └─────────┘ └─────────┘└─────────────────────────────────────┘

核心创新

  • • 情境化标签页:根据当前任务切换功能组
  • • 功能分组可视化:图标+文字,直观易懂
  • • 自适应空间:根据窗口大小调整显示
  • • 减少菜单层级:80%常用功能在第一层

而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:需要标准菜单栏/工具栏的软件 → 选择QMainWindowclass TextEditor(QMainWindow):    """文本编辑器,需要菜单栏保存文件"""    pass# 场景2:简单的配置对话框 → 选择QWidgetclass SettingsDialog(QWidget):    """设置对话框,不需要复杂的菜单结构"""    pass# 场景3:复杂应用的子窗口 → 选择QWidgetclass PreviewWindow(QWidget):    """预览窗口,作为主窗口的子窗口"""    pass

3. 混合使用示例

from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QWidgetimport sysclass 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?

  1. 1. 对话框和弹窗:确认框、消息框、设置窗口
  2. 2. 简单工具:计算器、单位转换器等小工具
  1. 3. 自定义控件:创建可重用的界面组件
  2. 4. 子窗口:主应用中的浮动面板

什么时候用QMainWindow?

  1. 1. 应用程序主窗口:编辑器、浏览器、IDE等
  2. 2. 复杂应用:需要标准菜单和工具栏的软件
  1. 3. 专业工具:图像处理、数据分析等专业软件

一个有用的技巧:从简单开始

# 初期:从QWidget开始,快速原型class SimpleApp(QWidget):    def __init__(self):        super().__init__()        # 简单布局和功能# 后期:需要更多功能时,轻松迁移到QMainWindowclass EnhancedApp(QMainWindow):    def __init__(self):        super().__init__()        # 将原来的QWidget内容设为中央部件        old_widget = SimpleApp()        self.setCentralWidget(old_widget)        # 添加菜单栏、工具栏等

总结

QWidget和QMainWindow不是"基础版"和"高级版"的关系,而是不同用途的工具

  • • QWidget像是白纸:给你最大自由度,想画什么就画什么
  • • QMainWindow像是已经画好框架的画布:提供了标准结构,你只需要填充内容
记住这个简单的选择原则
  • • 如果只需要一个简单的窗口或对话框 → 选QWidget
  • • 如果要创建有标准菜单/工具栏的应用程序主窗口 → 选QMainWindow

理解它们的区别后,你就能根据实际需求做出合适的选择,写出更专业、更高效的PyQt5代码!

5.3 信号与槽:Qt的"事件通信系统"

这是Qt最强大的特性之一!想象一下:

  • • 信号(Signal):像电灯的开关
  • • 槽(Slot):像电灯本身
  • • 连接(Connect):像连接开关和灯的电线工作原理
事件发生 → 发出信号 → 连接到槽 → 执行函数
import sysfrom PyQt5.QtWidgets import (    QApplication,    QWidget,    QVBoxLayout,    QLabel,    QPushButton,    QTextEdit,)from PyQt5.QtCore import pyqtSignalclass 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),槽函数接受并处理信号,在代码中就将处理结果展示在界面上:

5.4 常见控件快速上手

from PyQt5.QtWidgets import (    QLabel,          # 标签 - 显示文本或图片    QPushButton,     # 按钮 - 点击触发动作    QLineEdit,       # 单行输入框    QTextEdit,       # 多行文本编辑    QCheckBox,       # 复选框    QRadioButton,    # 单选按钮    QComboBox,       # 下拉框    QSpinBox,        # 数字输入框    QProgressBar,    # 进度条    QSlider,         # 滑块)

6. 让界面自动排列:布局管理

没有布局管理器的GUI就像没有CSS的HTML——元素会堆叠在一起。布局管理器其实就是把一堆空间按布局组合到一起。

为什么需要布局管理器?

  • • 自动调整控件位置和大小
  • • 适应不同分辨率和窗口大小
  • • 简化界面设计

常用布局管理器

QVBoxLayout - 垂直排列

from PyQt5.QtWidgets import QVBoxLayout, QPushButton, QLabellayout = QVBoxLayout()layout.addWidget(QLabel("第一行"))layout.addWidget(QPushButton("第二行"))layout.addWidget(QLabel("第三行"))self.setLayout(layout)  # 应用到窗口

QHBoxLayout - 水平排列

from PyQt5.QtWidgets import QHBoxLayoutlayout = 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)

7. 初学者常见错误与注意事项

错误1:忘记创建QApplication实例

# 错误写法window = QWidget()window.show()# 正确写法app = QApplication(sys.argv)window = QWidget()window.show()app.exec_()

错误2:在子线程中直接更新GUI

在PyQt5(以及大多数GUI框架)中,有一个黄金规则所有GUI操作都必须在主线程(也称为GUI线程)中进行!

为什么有这个限制?

简化的解释:PyQt5的GUI组件不是"线程安全"的想象一下两个线程同时修改同一个控件:

线程A: label.setText("Hello")     线程B: label.setText("World")    ↓                                   ↓同时写入同一个内存区域 → 数据竞争 → 程序崩溃!

错误示例分析

错误代码:

import sysimport timefrom PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButtonfrom PyQt5.QtCore import QThread, pyqtSignalclass 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. 1. 程序可能直接崩溃
  2. 2. 界面可能不更新
  3. 3. 可能偶尔正常工作,但不可靠(最危险的情况!)

正确解决方案:使用信号

方案1:QThread + moveToThread
import sysimport timefrom PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButtonfrom PyQt5.QtCore import QThread, pyqtSignal, QObjectclass 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 sysimport timefrom PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButtonfrom PyQt5.QtCore import QThreadPool, QRunnable, pyqtSignal, QObject, pyqtSlot, QMutexclass 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会自动:

  1. 1. 将信号放入主线程的事件队列
  2. 2. 主线程在适当的时候处理这个事件
  3. 3. 调用连接的槽函数(在主线程中!)

所以:

worker.signals.result.emit("数据")  # 子线程中发射信号    ↓PyQt5内部:跨线程传递信号    ↓label.setText("数据")  # 在主线程中执行槽函数

其他安全更新GUI的方法

1. 使用QTimer在主线程中轮询

from PyQt5.QtCore import QTimerclass 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, pyqtSlotclass 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))

最佳实践总结

  1. 1. 永远不在子线程中直接操作GUI控件
  2. 2. 使用信号/槽进行线程间通信
  3. 3. 复杂的计算放在工作线程中
  4. 4. GUI更新只在主线程中进行
  5. 5. 使用合适的错误处理机制
  6. 6. 使用moveToThread()确保对象在正确的线程中

简单记忆法则

记住这句话:

"信号发射是自由的,但槽函数执行总是在主线程的怀抱中。"

这样,您就能安全地在PyQt5中使用多线程了!

错误3:内存泄漏(忘记设置父对象)

# 可能的内存泄漏def create_widget():    widget = QWidget()  # 没有父对象,需要手动管理    return widget# 更好的做法def create_widget(parent=None):    widget = QWidget(parent)  # 指定父对象,自动管理内存    return widget

重要注意事项:

  1. 1. 所有GUI操作必须在主线程
  2. 2. 使用布局管理器,而不是固定坐标
  3. 3. 合理使用信号与槽,避免过度耦合
  4. 4. 学习使用Qt Designer进行可视化设计

9. 总结

通过本文,你已经掌握了PyQt5的基础知识:

  • • ✓ 理解了PyQt5的基本架构
  • • ✓ 学会了创建窗口和基本控件
  • • ✓ 掌握了信号与槽机制
  • • ✓ 能够使用布局管理器排列控件
  • • ✓ 创建了第一个交互式应用

PyQt5的学习曲线可能有些陡峭,但一旦掌握,你将拥有创建强大桌面应用的能力。记住,最好的学习方式就是动手实践。从今天开始,尝试用PyQt5解决你遇到的实际问题吧!

编程不仅是学习语法,更是学习如何创造。 现在,你已经有了一把强大的锤子(PyQt5),去建造属于你自己的数字世界吧!

引用链接

[1] Windows系统无法直接用uv安装pyqt5,但可以用uv pip安装-CSDN博客: https://blog.csdn.net/PolarisRisingWar/article/details/155969724

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-02-08 19:41:24 HTTP/2.0 GET : https://f.mffb.com.cn/a/462844.html
  2. 运行时间 : 0.085309s [ 吞吐率:11.72req/s ] 内存消耗:4,843.08kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=ebf4473c396166de80127deffa7e547a
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000419s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000656s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000785s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000278s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000480s ]
  6. SELECT * FROM `set` [ RunTime:0.000593s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000572s ]
  8. SELECT * FROM `article` WHERE `id` = 462844 LIMIT 1 [ RunTime:0.000993s ]
  9. UPDATE `article` SET `lasttime` = 1770550884 WHERE `id` = 462844 [ RunTime:0.001298s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 66 LIMIT 1 [ RunTime:0.000220s ]
  11. SELECT * FROM `article` WHERE `id` < 462844 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.003573s ]
  12. SELECT * FROM `article` WHERE `id` > 462844 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001724s ]
  13. SELECT * FROM `article` WHERE `id` < 462844 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.002418s ]
  14. SELECT * FROM `article` WHERE `id` < 462844 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000949s ]
  15. SELECT * FROM `article` WHERE `id` < 462844 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.003391s ]
0.086927s