
“选 PyQt6 还是 PySide6这个问题我在好几个技术社区里都见过几乎每次都能吵起来。同样是从 Python 里调 Qt同样能写出一样的窗口界面为什么会有两套名字如此相近的库更奇怪的是一直有人在这两个库之间来回迁移迁完了还常出现“代码怎么跑不起来了”的求救帖。其实我自己当年也卡在这个选择题上过。最早接触 Python GUI 开发时用 PyQt5后来公司要交付一个闭源工具许可证审查时发现情况不对费了好大劲换成 PySide2。再往后 Qt 官方推出 PySide6就一直用到现在。并不是说 PyQt6 不值得用而是这两个库背后的定位、授权、工具链确实存在几条关键差异如果不把这些差异摆到桌面上看很容易拍脑袋选完再后悔。这篇文章就从一个实际开发者视角把两个库从出身、许可、API、工具链到最终选型建议一次讲透。适合刚准备用 Python 写桌面应用的新手也适合团队里正在做技术选型或准备从 PyQt5 往 Qt6 迁移的开发者读完你至少能明确自己该往哪边靠。先声明一句不管选哪个能写出可维护的界面代码才是正事但选型错误带来的返工成本确实太高值得花几分钟把这事想清楚。以下从一个实际开发者视角把两个库从出身、许可、API、工具链到最终选型建议一次讲透。适合刚准备用 Python 写桌面应用的新手也适合团队里正在做技术选型或准备从 PyQt5 往 Qt6 迁移的开发者读完你至少能明确自己该往哪边靠。先声明一句不管选哪个能写出可维护的界面代码才是正事但选型错误带来的返工成本确实太高值得花几分钟把这事想清楚。”1. 两个库的出身为什么同一个 Qt 会冒出两套 Python 绑定1.1 底层只有一套 Qt但 Python 封装走了两条路要理解 PyQt6 和 PySide6 为什么并存首先得搞明白 Qt 本身是什么。Qt 是一套用 C 写的跨平台界面框架所谓“用 Python 写 Qt 程序”本质上是在 Python 进程里加载 Qt 的 C 动态库再通过一层封装把 C 对象映射成 Python 对象。这层封装就是 Python 绑定。理论上 Qt 官方只维护 C 版本Python 绑定可以由任何人来做。于是历史上出现了一位“吃螃蟹”的开发者从 1998 年就开始维护 PyQt一直做到 PyQt5、PyQt6是 Python 社区里最老牌、最成熟的 Qt 绑定。很多早期的 Python GUI 教程、开源项目、企业内部工具都建立在它之上。这也是为什么到今天还有大量存量代码跑在 PyQt 系上网上随便搜一个“Python 桌面应用”教程十有八九是 PyQt5 写的。后来情况发生了变化。Qt 官方自己也觉得 Python 这么热不能全靠第三方于是开始推动官方 Python 绑定项目推出了 PySide。早期版本命运比较波折一度因为公司战略调整而停摆直到 Qt6 时代才真正稳定下来形成了现在与 Qt 版本号完全一致的 PySide6。所以 PyQt6 与 PySide6 的关系不是说哪个是山寨而是“第三方老前辈”和“官方亲儿子”两条路线最终在 Qt6 时代交汇了。1.2 官方亲儿子 Vs 老牌悍将各自的优势是什么PySide6 作为 Qt 官方项目最大的优势是版本节奏和官方文档完全对齐。Qt 官方文档里的 Python 示例默认就是 PySide6你照着文档写代码不需要做任何“翻译”工作。同时它的更新跟着 Qt 大版本走基本能做到当天同步遇到新特性或者新模块PySide6 总是最先吃到螃蟹。PyQt6 的优势则在于“老”和“稳”。它经过了二十多年迭代各种边缘 case 处理得非常娴熟在对 Qt 类库的覆盖上也没有明显缺口。如果你在 PyQt5 时代已经积累了成熟代码团队里每个人都习惯 PyQt 的写法那么继续用 PyQt6 的学习成本几乎为零。很多老牌开源项目、工业控制软件、科研工具代码库到现在依然跑在 PyQt6 上这不是没有原因的。这两者的底层依赖其实是同一个 Qt C 库渲染引擎完全一样所以不存在“谁画出来的界面更漂亮”这种事。差别只在封装层、许可条款、工具链和社区生态这些看不见的地方而正是这些看不见的地方决定了你后面几个月的开发体验。2. 最大的分水岭许可协议到底差在哪2.1 GPL 和 LGPL一句话说清两份协议的意思如果你去翻两个项目的官方页面会看到 PyQt6 采用的是 GPLv3 加上商业授权双许可模式而 PySide6 采用的是 LGPLv3 为主的开源许可。两套协议名字只差一个 L实际含义却差得非常大这是整个选型里最关键的一个决策点。GPL 的核心约束是“只要你的程序里包含了 GPL 代码你的程序整体上也必须按 GPL 发布也就是要开源”。用大白话说你可以用 PyQt6 做任何内部工具但一旦你要把做出来的程序分发出去而程序又包含 PyQt6 的代码那么整个程序的源代码基本都得对外公开。LGPL 宽松很多。LGPL 同样允许你调用这个库但允许你的程序保持闭源前提是使用者能够替换掉这个 LGPL 库本身。Python 里的导入机制天然就是动态的用户想换一个 PySide6 库只需要重新安装一份这不难满足所以 LGPL 对商业项目非常友好。我补充一句我不是律师具体法律判断要咨询专业人士但这是业内普遍公认的理解。2.2 对商业项目的实际影响闭源发布时的关键区别如果你的目标是做一个闭源商业软件要卖给客户或放到应用商店里PyQt6 的 GPL 就是一个绕不开的坎。理论上你不开源就没有合法分发权除非花一笔钱购买 Riverbank 的商业授权。PySide6 则没有这个负担闭源出售、内部部署、作为 SaaS 服务跑在服务器上这些场景都相对干净。有人可能会说“我做的软件只给公司内部人用不对外分发是不是无所谓”内部分发确实一般不构成 GPL 所约束的“分发”但这有很多灰色地带。比如你的团队给客户做外包项目交付时需要把软件安装包放到客户服务器上这算不算分发按交付场景看风险很高。再比如你的项目是给内部多个部门用的但通过安装包统一部署同样可能被认定为一种传播。现实中因为这种模糊边界而出问题的情况并不少见所以别急着默认自己“内部用就没事”。相反如果你的项目本来就是开源的发布许可证恰好是 GPL 或 AGPL那 PyQt6 的 GPL 完全可以接受。这种情况下 PyQt6 反而是很自然的选择因为开源社区里大量 GPL 项目都用 PyQt 系经过二十多年验证生态非常成熟。2.3 许可之外还会连带影响哪些环节许可的影响不止是“能不能发布”这么简单。很多企业的法务部门在采购软件组件审核时对 GPL 组件非常敏感一旦项目里有 GPL 依赖合规审批成本会直线上升。我见过一个团队界面都开发到一半了法务突然发现 PyQt5 的 GPL 条款有问题最后只能改绑定重写那叫一个酸爽。反过来LGPL 的 PySide6 因为许可友好成为很多商业软件项目的默认选择。你可以在不公开业务代码的前提下享受 Qt 的全部能力团队做事也踏实。另外要注意 PySide6 某些模块的许可可能不完全一样比如一些 Qt 商业模块在开源版本里有额外限制如果你要用到比较冷门的企业模块最好提前看一下模块级别的说明。主框架是 LGPL这一点大方向没错。3. API 对比90% 是同卵双胞胎剩下 10% 才是真正的坑3.1 基本代码几乎没差连缩进都差不多PyQt6 和 PySide6 做的是同一件事把同一个 Qt C 库暴露给 Python。所以你会看到两套库的类名、函数签名、信号机制都出奇地一致。写个最简单的窗口除了 import 语句不同其他地方基本可以复制粘贴。# PyQt6 版本 import sys from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton app QApplication(sys.argv) window QMainWindow() button QPushButton(点击我, window) button.clicked.connect(lambda: print(hello)) window.show() sys.exit(app.exec())# PySide6 版本 import sys from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton app QApplication(sys.argv) window QMainWindow() button QPushButton(点击我, window) button.clicked.connect(lambda: print(hello)) window.show() sys.exit(app.exec())这段代码唯一的不同就是第二行导入路径其它完全一样。这也是很多人觉得“这不就是改个名字嘛”的原因。往细看绝大部分常用控件、布局、对话框、样式表的调用方式都是一致的做日常开发时基本感觉不到差异。3.2 信号槽与自定义信号两边各有一套命名Qt 的信号槽机制是它的灵魂Python 绑定里同样如此。PyQt6 里自定义信号用pyqtSignalPySide6 里叫Signal槽装饰器一个是pyqtSlot一个是Slot。虽然名字不一样但用法和参数声明方式几乎相同。# PyQt6 from PyQt6.QtCore import pyqtSignal, pyqtSlot class Counter(QWidget): value_changed pyqtSignal(int) pyqtSlot(int) def set_value(self, v): self._v v self.value_changed.emit(v)# PySide6 from PySide6.QtCore import Signal, Slot class Counter(QWidget): value_changed Signal(int) Slot(int) def set_value(self, v): self._v v self.value_changed.emit(v)看起来就是大小写和单词替换但这里有个坑如果你从 PyQt5 迁移到 PySide6全局搜索替换pyqtSignal到Signal时如果代码里有大量自定义信号很容易漏掉某个位置或者替换到注释里。我的经验是迁移代码时不要手动硬替换先跑一遍测试用例用报错驱动修改。内置信号也需要注意。Qt 里很多控件信号都有重载比如QComboBox.currentIndexChanged既可以是int参数也可以是str参数。PyQt6 和 PySide6 都支持用下标的方式显式指定要连接的重载版本combo.currentIndexChanged[int].connect(lambda idx: print(idx))不用下标也能连但如果回调函数写得不严谨两个库对重载信号的处理细节会有微妙差异容易出现“在 PyQt6 上正常、切到 PySide6 就报错”的情况。统一写成显式下标形式两边行为最一致。3.3 枚举、exec 和模块名老代码迁移最容易踩的三个坎从 PyQt5 或 PySide2 迁到 Qt6 时代最大的变化其实是枚举。PyQt5 里你经常可以看到Qt.AlignCenter、Qt.blue这种写法到了 PyQt6 和 PySide6都强制要求写成完整枚举Qt.AlignmentFlag.AlignCenter、Qt.GlobalColor.blue。这跟选 PyQt6 还是 PySide6 无关而是 Qt6 C 层的 API 风格变了两个绑定都跟随。老代码里凡是用了旧枚举写法的地方迁移时都得手动改。另一个常见差异是exec()。PyQt5 时代因为exec是 Python 2 的保留字所以绑定里提供了exec_()方法很多人到现在还记得弹窗要写dialog.exec_()。PyQt6 和 PySide6 里都统一为exec()两种绑定表现一致但网上老的博客教程还在教exec_()新手抄了就会碰到AttributeError。模块名也要注意。拿图表模块举例PyQt6 里是from PyQt6.QtCharts import ...PySide6 里是from PySide6.QtCharts import ...看起来只是前面的库名不同。但 Qt 自身某些模块本身命名就带不带 s比如QtDataVisualization三个库都一样叫这个名字。真到迁移时建议先列一个项目里用到的模块清单逐个对比导入路径别只在报错时修一个地方就完事。3.4 想写一套代码两边通吃试试兼容层很多开发者会问如果我写一个开源库希望用户自己选择 PyQt6 或 PySide6有没有办法让代码不绑定死某一个答案是有的比如生态中常见的 QtPy 兼容层。它做的事情其实很简单根据环境变量QT_API的值决定最终从哪个库导入。import os os.environ[QT_API] pyside6 # 或者 pyqt6 from qtpy.QtWidgets import QApplication, QMainWindow from qtpy.QtCore import Slot, Signal用 QtPy 抽象层写代码后框架代码本身不直接依赖任何一套绑定最终用户推什么绑定程序就用什么绑定。这个方案尤其适合给第三方使用的基础库、插件系统或者你不想替使用者做选型决定的时候。不过我要泼一盆冷水如果你是在写一个独立应用而不是一个要被别人引用的库我不太建议用兼容层。兼容层会多一次转发调用增加调试复杂度而且两个库的重载、枚举、信号解析在某些细节上仍可能表现不同你不可能每一处都测到。独立项目直接锁死一个绑定开发体验最痛快。4. 工具链与生态把项目交给哪个库更省心4.1 安装大小、自带工具链与日常工作效率从 pip 安装角度来看两者差不多PySide6 的安装包会稍微大一点因为它把 Qt 库、QML 组件、翻译文件等绑在一起动辄几百 MB。PyQt6 的安装包也不小都是“装完就自带完整 Qt 运行时”的思路所以在小带宽环境里安装体验谈不上好。真正拉开差距的是配套工具。PySide6 安装后自带一整套命令行工具比如pyside6-uic可以把.ui文件转成 Python 代码pyside6-rcc编译资源文件pyside6-designer启动 Qt Designer还有pyside6-linguist和pyside6-deploy。这些工具和 Qt 官方工具链直接配套你在 Qt Creator 里画好界面拿命令行一转就能用。PyQt6 的情况就不太一样。PyQt6 本体安装后是不带 Qt Designer 界面的你自己得另外装 Designer 或者用 Qt Creator。虽然可以通过额外工具包补上但整体工具链的完整性和一圈程度明显不如 PySide6 那边顺畅。尤其团队里如果是新手让他在一堆命令行脚本里找 designer体验会很痛苦。4.2 打包部署PyInstaller 能通吃但 PySide6 有官方加持桌面应用开发最后一步必然是打包。PyInstaller 对 PyQt6 和 PySide6 都提供了 hook所以用 PyInstaller 打包这两个库都能跑区别不大。但如果你用的比较多 QML、插件、动态加载模块PySide6 这边会相对顺利一些因为它自带pyside6-deploy能根据你的项目自动收集 QML 插件、翻译文件、Qt 运行时依赖减少很多手工折腾。PyQt6 通常会依赖 PyInstaller 社区维护的 hook 和网上各位前辈踩坑经验遇到问题只能靠搜。遇到新版 Python 刚发布PyInstaller 还没跟上时两个库可能都会出问题但 PySide6 因为有官方团队持续跟进兼容速度一般会更快。还有一个容易踩的坑PyInstaller 打包时很容易把 PyQt6 和 PySide6 的组件同时收进包里最后启动时报 DLL 冲突。如果你的环境里两个库都装过打包前最好开一个干净虚拟环境只装目标库能减少很多灵异问题。4.3 社区与文档遇到问题谁帮你更快这是很多人低估的一项。Qt 官方文档里的 Python 示例基本都采用 PySide6所以如果你用 PySide6文档里的代码可以直接抄、直接跑非常省时间。而 PyQt6 的用户如果看到官方示例还得把 import 和部分 API 写法手动“翻译”一遍虽然不复杂但每天重复做这件事就很烦。论社区积累量PyQt 系因为历史长在 Stack Overflow 等平台的问答存量明显更大。遇到一些冷门控件的疑难杂症搜 PyQt 能找到的信息往往更多。不过 PySide6 这几年热度上升很快加上官方背书新问题最终基本也能被覆盖到。我的体感是日常 API 问题两边都差不多但如果你想看官方最新的示例、想提交 bug 让官方修PySide6 的路径更直接。4.4 性能要不要考虑别把性能当成选型筹码有人担心 PySide6 是官方项目会不会更耗资源或者 PyQt6 是不是代码生成更高效。实际上两个绑定都只是把 Python 调用转到 C 层界面渲染、事件循环、布局计算这些耗时大头都在 Qt C 核心层真正在 Python 绑定层消耗的时间占比非常小。有一种经典误区是“某个库跑起来更流畅”这多半是心理作用或者测试代码写得有偏差。真正影响响应速度的反而是你自己的代码比如在信号槽里做耗时的数据计算、频繁更新控件文本、大量使用 Python 层循环。所以我的经验是性能因素在 PyQt6 和 PySide6 之间可以直接忽略把时间花在优化业务逻辑和减少不必要的 UI 刷新上收益大得多。5. 落到你的项目上到底应该选哪个一个可操作的决策框架5.1 先列几个问题别急着站队与其到处问别人“你用什么”不如先静下来回答这几个问题答案会直接帮你定位你的项目最终会怎么发布闭源商业、开源、内部工具还是交付给客户法务或客户是否有开源许可证偏好团队里现有代码和技术栈是哪一套是否已经积累了大量 PyQt 代码你需要用 Qt 的那些模块QML、Charts、3D、渲染这些你们更喜欢跟着 Qt 官方版本走还是希望绑定层更保守稳定这些问题的优先级也不是完全并列的。第一个和第三个通常最影响决策其次才是第五个。如果你是一个独立开发者就更是简单先看发布方式再看你熟不熟悉的 API 风格。5.2 一张表把差异压实对比维度PyQt6PySide6开发背景第三方老牌实现历史悠久Qt 官方项目版本同步快主要许可GPLv3 商业授权LGPLv3闭源商业分发需商业授权有门槛相对友好通用选择官方文档示例非默认语言需手动转换默认 Python 示例自带工具链需要配置第三方支持自带全套工具集成度高社区存量资料多年积累内容更多官方项目增长很快与 Qt 新版本同步滞后时间不确定基本同步性能差异与 PySide6 基本持平与 PyQt6 基本持平迁移成本从 PyQt5同系迁移较熟悉差异小但需改导入这张表的重点不在项目数量而在“许可”和“文档”两行。大多数商业项目看到这两列心里基本就有数了。5.3 我的建议路径直接给你一个可复用的选择标准按我这些年的习惯新项目直接用 PySide6理由很简单许可省心、官方文档拿来即用、工具链完整、打包部署的配套也齐全。这不是说 PyQt6 不行而是对一个没有历史包袱的新项目来说PySide6 的综合阻力最小。如果团队里已经有一套跑得好好的 PyQt5/PyQt6 代码那就别为了追求“官方”而强行重构。代码能跑、大家能维护本身就是最大的价值。这种情况下继续用 PyQt6 完全合理只需要注意新写的代码不要混进 PySide6 的导入。如果项目是开源且本来采用 GPL 系协议PyQt6 也是自然选择。一旦做了决定就不要再左右摇摆。我见过太多人先是选了 PyQt6写了一阵觉得 PySide6 工具链更顺手又切过去结果迁移时因为枚举、信号重载这些细节改到怀疑人生。两个库毕竟不是完全相同的代码来回切等于把前一个阶段积累的 Bug 修复经验全部作废。定下来往前冲比纠结选型更重要。最后再补一句实操建议如果暂时还没法拍板先在干净虚拟环境里安装 PySide6把最小界面跑起来再装 PyQt6跑同一个界面体感一下两者的工具链和报错风格花不了半小时但你的直觉会告诉你答案。