资讯详情

基于PyQT6从零开始做一个计时器

📅 2026/10/11 14:43:05 | 华诺云谱 👁 阅读
基于PyQT6从零开始做一个计时器
前言PyQt6 是 Qt 6 的 Python 绑定属于第三方库要pip install PyQt6才能用本机没有安装环境所以本文代码只能逐行推演。官方文档写明 PyQt6 要求Python 3.9 或更高如果你还在用 3.8就只能退回 PyQt5。本文用「做一个秒表」这条主线把 PyQt6 的窗口、布局、信号槽、定时器串一遍。具体 API 与参数以 PyQt6 官方文档为准。这里必须先泼一盆冷水网上绝大多数 PyQt 教程是照着 PyQt5 写的直接抄到 PyQt6 上会报错。这不是玄学而是 PyQt6 和 PyQt5 之间有一批真实的破坏性变更。官方文档《Differences Between PyQt6 and PyQt5》明确列出所有具名枚举named enum现在都是 Python 标准库Enum的子类exec_()和print_()被移除qApp被移除Qt模块被移除。所以本文的示例会刻意写成 PyQt6 的写法并对照指出 PyQt5 的老写法错在哪里。一、从零开始一个窗口的最小骨架Qt 程序的最小骨架是四步创建 QApplication → 创建窗口QWidget / QMainWindow→ show() → app.exec()# 适用于 Python 3.9需 pip install PyQt6第三方库import sysfrom PyQt6.QtWidgets import QApplication, QLabeldef main():app QApplication(sys.argv) # 一个进程只需要一个label QLabel(你好PyQt6)label.setWindowTitle(最小窗口)label.resize(280, 120)label.show()sys.exit(app.exec()) # PyQt6exec()不是 exec_()if __name__ __main__:main()QApplication负责初始化 Qt、管理事件队列是app.exec()里那个事件循环的宿主。sys.exit(app.exec())把exec()的返回码当作进程退出码——官方文档说明exec()会一直阻塞到exit()被调用并返回传给exit()的值。QWidget既能当独立窗口也能当容器基类QMainWindow自带菜单栏、工具栏、状态栏的框架做复杂程序时用。二、信号与槽Qt 的事件绑定方式Qt 不用bind那一套它用信号与槽signals and slots控件发出信号你把自己的可调用对象连上去。# 适用于 Python 3.9需 pip install PyQt6第三方库from PyQt6.QtWidgets import QPushButtonbutton QPushButton(开始)button.clicked.connect(self.start) # clicked 是信号self.start 是槽文档写明QPushButton在鼠标点击、空格键或快捷键激活时发出clicked()信号另外还有pressed()、released()。clicked.connect(...)返回一个连接对象需要断开时可以调用它的disconnect()。信号可以在连接时传参数也可以不传连接的目标既可以是普通函数、lambda也可以是加了装饰器的方法具体重载写法以官方文档为准。三、定时器QTimer 与「不阻塞事件循环」秒表的关键是定时刷新界面。Qt 的做法是QTimer。官方文档说明创建一个QTimer把它的timeout信号连到槽上再调用start()它就会按固定间隔反复发出timeout。间隔用setInterval()设置。# 适用于 Python 3.9需 pip install PyQt6第三方库from PyQt6.QtCore import QTimertimer QTimer(self)timer.setInterval(30) # 毫秒timer.timeout.connect(self.on_tick)timer.start()这里有个跨工具包通用的道理QTimer的回调由事件循环调用所以回调里只要做「读一下时间、改一下标签」这种瞬间完成的事界面就始终畅通。反过来如果在on_tick里做重活事件循环同样会被卡住。四、完整示例带开始 / 暂停 / 重置的秒表把窗口、布局、信号槽、定时器拼起来。计时不用「累加每次 30 毫秒」的办法会累积误差而是记录起点、用time.monotonic()求差。# 适用于 Python 3.9需 pip install PyQt6第三方库import sysimport timefrom PyQt6.QtCore import Qt, QTimerfrom PyQt6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget,)class Stopwatch(QWidget):def __init__(self):super().__init__()self.setWindowTitle(PyQt6 秒表)self._running Falseself._accumulated 0.0 # 之前几段累计的秒数self._started_at 0.0self.time_label QLabel(00:00.00)# PyQt6枚举必须写全限定名 Qt.AlignmentFlag.AlignCenterself.time_label.setAlignment(Qt.AlignmentFlag.AlignCenter)self.time_label.setStyleSheet(font-size: 30px;)self.start_btn QPushButton(开始)self.reset_btn QPushButton(重置)row QHBoxLayout()row.addWidget(self.start_btn)row.addWidget(self.reset_btn)column QVBoxLayout(self)column.addWidget(self.time_label)column.addLayout(row)self.timer QTimer(self)self.timer.setInterval(30)self.timer.timeout.connect(self._on_tick)self.start_btn.clicked.connect(self._toggle)self.reset_btn.clicked.connect(self._reset)def _toggle(self):if self._running:self.timer.stop()self._accumulated time.monotonic() - self._started_atself.start_btn.setText(继续)else:self._started_at time.monotonic()self.timer.start() # 按 setInterval 的间隔开始发 timeoutself.start_btn.setText(暂停)self._running not self._runningdef _on_tick(self):total self._accumulated (time.monotonic() - self._started_at)self.time_label.setText(self._format(total))def _reset(self):self.timer.stop()self._running Falseself._accumulated 0.0self.start_btn.setText(开始)self.time_label.setText(00:00.00)staticmethoddef _format(seconds):minutes, rest divmod(seconds, 60)whole, frac divmod(rest, 1)return f{int(minutes):02d}:{int(whole):02d}.{int(frac * 100):02d}def main():app QApplication(sys.argv)window Stopwatch()window.resize(280, 150)window.show()sys.exit(app.exec())if __name__ __main__:main()几个逐行推演要点。QVBoxLayout(self)直接把布局设为窗口的顶层布局等价于先建布局再self.setLayout(column)addLayout用来把一个子布局塞进布局里形成嵌套。_format用divmod把秒拆成「分」和「余下的秒」再把余下的秒拆成整数部分和小数部分乘 100 取两位百分秒divmod(seconds, 60)里seconds是浮点所以分和秒都是浮点最后统一int()再格式化。计时基准是time.monotonic()它是单调时钟不受系统时间被改动的影响。五、PyQt6 与 PyQt5 的真实差异本文最该记住的一段差异点PyQt5 老写法PyQt6 正确写法枚举访问Qt.AlignCenter不限定枚举名Qt.AlignmentFlag.AlignCenter必须写全限定名事件循环app.exec_()app.exec()打印相关printer.print_()该方法已被移除全局应用对象qApp已移除自己保存QApplication实例模块Qt模块已移除枚举类型基础混用IntEnum与自定义类型全部是 PythonEnum子类可组合的标志位是Flag子类原因在官方文档里写得很清楚PyQt6 把所有具名枚举实现成标准Enum的子类并取消了 Qt5 那种「枚举值可以直接从命名空间拿到」的兼容写法所以Qt.AlignCenter在 PyQt6 里找不到必须写成Qt.AlignmentFlag.AlignCenter。同理Qt 6 本身移除了一批 Qt 5 已废弃的 APIPyQt6 也不提供。照抄 PyQt5 教程时最常见的报错就是AttributeError: type object Qt has no attribute AlignCenter。常见坑点❌ 抄 PyQt5 教程写app.exec_()。✅ PyQt6 里exec_()已被移除要写app.exec()。❌ 写label.setAlignment(Qt.AlignCenter)。✅ PyQt6 具名枚举是Enum子类必须全限定Qt.AlignmentFlag.AlignCenter。❌ 在槽函数里跑长循环或发同步网络请求。✅ 槽由事件循环调用长时间占用会让界面假死耗时任务交给QThread且注意工作线程不能直接碰控件要用信号把结果发回主线程。❌ 定时器里用「每次加一个间隔」来累计时间。✅ 定时器不保证严格准点官方文档也说明系统忙时可能延迟要记起点用time.monotonic()求差避免误差累积。❌ 忘记调用show()或者忘记app.exec()。✅ 不show()窗口不可见不exec()事件循环不启动窗口一闪即没。❌ 程序退出后定时器还在跑界面已销毁导致访问已释放对象。✅ 退出路径里先timer.stop()并把QTimer的父对象设为控件让销毁顺序被 Qt 管理。❌ 把QApplication当成普通对象创建多次。✅ 一个进程通常只需要一个QApplication它是全局状态重复创建会出问题。总结要点结论安装PyQt6 是第三方库需 pip 安装要求 Python 3.9骨架QApplication→ 窗口 →show()→exec()事件绑定信号与槽button.clicked.connect(handler)定时刷新QTimersetInterval()timeout信号计时基准用time.monotonic()求差不要累加间隔最大坑PyQt5 教程不能直抄枚举全限定、exec_改exec从零做一个计时器代码量不大但它把「事件循环、信号槽、定时器、布局」四件事都过了一遍。真正值得你带走的是最后一节那张对照表——它能让你的 PyQt6 代码不在第一行就崩。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑