ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PyQt6 vs PySide6:许可证与工具链决定怎么选

PyQt6 vs PySide6:许可证与工具链决定怎么选 “PyQt 还是 PySide”这个问题我至少被问过二十次。上个月 A同学拿着新项目的需求文档来找我说准备做一个跨平台的内部工具先前用 Pyqt5 写过几个脚本现在看新项目不知道该迁到 PyQt6 还是直接上 PySide6。我告诉他这个问题没有标准答案但有非常清晰的判断逻辑关键在于你对许可证、官方支持、工具链和代码迁移成本这四件事分别愿意付出什么。先说结论如果你问我“现在新开项目选哪个”我会毫不犹豫推荐 PySide6。但这不是一个“谁好谁坏”的问题而是“谁更适合你这个项目的运行环境”的问题。PyQt6 有它的历史优势PySide6 有它的官方便利搞懂差异背后的原因你就能在十分钟内做出决定而不是反复横跳。1. 先搞清楚这两个库到底是什么关系很多新手以为 PyQt6 和 PySide6 是两个完全不同的图形框架这是最大的误解。它们绑定的底层对象是同一个——Qt 6这是一个由 C 编写的跨平台 GUI 框架只不过 PyQt6 和 PySide6 各自封装了一套 Python 接口相当于同一个底层引擎配了两套不同的方向盘和仪表盘。1.1 核心引擎完全一致Python 层才是差异点Qt 6 本身提供了完整的控件库、布局系统、事件循环、网络模块、数据库驱动、多媒体支持等等这些底层能力不区分 PyQt6 还是 PySide6因为两者最终调用的都是同一套 C 类库。你在 PyQt6 里写一个QMainWindow在 PySide6 里写一个QMainWindow底层实例化的是同一个 C 对象。真正不同的是负责“把 Python 调用翻译成 C 调用”的绑定层。PyQt6 使用的是一个叫 SIP 的绑定生成器PySide6 使用的是官方维护的 Shiboken。这两个绑定生成器的设计思路略有区别导致暴露给 Python 的 API 在细节上有细微差异但绝大多数场景下你的代码在两者之间只需要改 import 语句和个别方法名就能跑通。1.2 历史渊源一个“老资格”一个“后来居上”PyQt 的资历比 PySide 老得多在 Qt5 时代PyQt5 几乎是 Python GUI 开发的事实标准大量教程、开源项目、公司内部代码都是基于 PyQt5 写的。我记得早几年做桌面端工具遇到问题谷歌一搜十个结果里八个是 PyQt5 的写法就算你在用 PySide2也往往要靠看 PyQt5 的问答来解决问题。PySide 一开始是由开源社区发起的项目目标是提供更宽松的许可证后来被 Qt 官方接手维护从 PySide2 开始逐步补齐到 PySide6 的时候无论是 API 完整性、工具链还是文档都已经和 PyQt6 平起平坐甚至在“官方支持”这个维度上反超了 PyQt6。这件事非常重要PySide6 的更新节奏是跟着 Qt 官方版本走的Qt 官方发一个新版本PySide6 很快同步而 PyQt6 的跟进时间未必那么及时。如果你很在意能用上最新的 Qt 特性这一点值得放进决策因素里。但历史积累的优势也不能忽视PyQt6 的社区案例、开源项目、第三方教程数量仍然庞大很多老项目里积累了针对 PyQt 的成熟封装如果你打算站在别人的代码基础上快速起步PyQt6 的资源密度更大。2. 授权模式这个差异会在你发布程序时浮出水面许可证是 PyQt6 和 PySide6 之间本质性的差别也是大多数人最容易忽略的地方。很多人在本地写 demo 的时候完全感觉不到区别等到了要把程序发给客户或者部署到公司内部服务器的时候才发现许可证成了一个绕不过去的问题。2.1 PyQt6 的 GPL 限制PyQt6 采用的是 GPL 许可证这意味着你基于 PyQt6 写出来的程序在分发的时候必须遵守 GPL 的传染性条款。简单来说如果你把程序分发出去就需要以同样的 GPL 许可证开源整个项目的源码或者向 PyQt6 的授权方购买商业许可证才能闭源发布。我用大白话解释一下你自己写了个自动化工具用 PyQt6 做了界面发给公司内部同事用那这个“内部使用”通常不构成向外分发GPL 的压力相对小。但如果你打算把它作为商业软件卖出去又不打算开源就必须拿到 PyQt 的商业授权这会产生一笔费用。如果你是在一个开源项目里用 PyQt6这个限制影响不大因为项目本来就是要开源。但如果公司法规部门审查严格光是走一遍许可证流程就够你折腾一阵。2.2 PySide6 的 LGPL 更友好PySide6 的许可证是 LGPL v3它的核心优势在于允许你在不修改 Qt 库本身的前提下将程序以闭源形式发布。你不欠任何人源码不需要购买商业授权只需要满足几个相对简单的要求比如在程序中声明使用了 LGPL 库保留版权信息并且如果你修改了 Qt 的 C 库需要把修改后的库代码公开。这里重点在于你直接用的是 Python 绑定你几乎没有机会去改 Qt 的 C 源码所以 LGPL 这条传染路径基本不会命中你。我这么讲可能有点抽象做一个实际操作层面的对比对比项PyQt6PySide6开源许可证GPL v3LGPL v3闭源商业发布需要购买商业授权允许需要开源应用源码一般情况下需要不需要修改底层库源码限制较多需要公开修改后的底层库适合场景开源项目、个人工具、可接受 GPL商业软件、闭源产品、内部工具我在某公司做项目的时候生产环境里有一个给客户交付的桌面客户端一开始用的是 PyQt5后来准备升级到 PyQt6被法务团队拦住问了一堆许可证相关问题最后还是切到了 PySide6。这件事给我的教训就是技术选型的时候先把许可证这条路走通别等代码都写完了再来擦屁股。2.3 对个人开发者的现实意义如果你只是做个人工具、学习项目、开源框架PyQt6 的 GPL 几乎不构成障碍。但如果你要做独立开发者、接外包、做商用软件PySide6 显然是更稳妥的选择。我在帮朋友评估一个商业软件项目时看到他们把 PyQt6 放在技术选型的第一位我直接建议换成 PySide6理由很简单许可证风险是项目里最容易量化也最不该赌的部分而 PySide6 把它彻底清零了。3. API 差异比想象中小但坑都在细节里很多人在 PyQt6 和 PySide6 之间纠结以为换一个库就要重写代码。真实情况是90% 的代码可以直接平移你只需要改导入语句、几个信号槽的写法、个别枚举的引用方式。3.1 导入语句和版本信息的差异最直观的差异就是包名。PyQt6 的包前缀是PyQt6PySide6 的包前缀是PySide6。写一个最基础的窗口程序两者只有导入行不同# PyQt6 写法 from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton # PySide6 写法 from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton版本信息的获取方式也略有区别。PyQt6 你通常用PyQt6.QtCore.PYQT_VERSION_STR和QT_VERSION_STRPySide6 则直接用PySide6.__version__或者PySide6.QtCore.__version__。这个区别在写调试日志的时候要注意别把两者记混。3.2 枚举类型新写法两边都有但兼容性不同Qt6 把枚举类型整理得更规范了PyQt6 和 PySide6 都支持新的作用域枚举写法from PySide6.QtCore import Qt from PyQt6.QtCore import Qt # 两者都支持 align Qt.AlignmentFlag.AlignCenter button Qt.MouseButton.LeftButton但在 PyQt6 里旧式的写法基本上不可用了像Qt.AlignCenter这种不写作用域的调用会直接报错。PySide6 对部分旧写法的容忍度稍微高一些但它也一直在推动你使用新写法。这里我给个建议你写新项目时一律使用带AlignmentFlag、MouseButton这种完整作用域的枚举格式这样代码在两边都能干净运行。3.3 信号槽一个最容易踩的隐藏差异信号槽是 Qt 的核心机制两边都支持装饰器和信号声明。PyQt6 里常用pyqtSignal和pyqtSlotPySide6 里是Signal和Slot功能等价名字不同。但在连接重载信号时有个真实的坑。比如QComboBox的currentIndexChanged信号它同时有整数参数和字符串参数两种重载在 C 里可以靠参数类型自动匹配在 Python 这边有时会出现混淆。PyQt6 和 PySide6 在默认情况下的处理并不完全一样我曾经在 PySide6 里遇到连上信号后回调函数拿到的参数类型不对的情况排查了半天才发现是重载信号没有显式指定类型。稳妥的做法是遇到重载信号时把类型写死在连接语句里combo QComboBox() # PySide6 combo.currentIndexChanged[int].connect(on_index_changed) # PyQt6 同样可以这样显式指定 combo.currentIndexChanged[int].connect(on_index_changed) def on_index_changed(index: int): pass另外一个相关的问题是在 PySide6 里如果你定义自己的类并声明了信号重载最好用Slot()装饰器标明参数类型否则 Python 的弱类型特性会让连接变得混乱。PyQt6 的pyqtSlot()装饰器作用相同虽然没有强制要求但多写一行装饰器能省掉很多后续调试时间。3.4 面向兼容性的迁移思路如果你已经有 PyQt5 / PyQt6 的代码想迁到 PySide6不需要重写业务逻辑只需要做四件事替换 import 来源、将pyqtSignal改名为Signal、pyqtSlot改名为Slot、将exec_改为exec。反过来从 PySide 迁到 PyQt 也同理。我做过一次实际迁移一个三百多行的界面模块前后只花了一个小时大部分时间都花在处理枚举值和信号参数类型上。4. 工具链和打包体验PySide6 确实更省心代码层面的差异只是表象真正影响开发效率的是工具链。这一点上我必须说PySide6 的表现比 PyQt6 更像一个现代 Python 生态项目。4.1 官方工具集的开箱即用程度PySide6 在安装时就会自动带上与 Qt 官方工具对应的命令行工具包括pyside6-designer、pyside6-uic、pyside6-rcc、pyside6-linguist。这意味着你装完pyside6这个包就直接拥有了可视化的界面设计器、UI 文件转换器、资源文件编译器和翻译工具。比较常见的操作流程是在 Qt Designer 里画好界面保存成.ui文件然后执行pyside6-uic mainwindow.ui -o ui_mainwindow.py生成的 Python 文件可以直接 import配合.ui文件里的布局和控件代码量可以大幅减少。PyQt6 这边的情况就麻烦一点因为 PyQt6 的 pip 包装完以后并不会自带一个独立的 Designer 可执行程序你需要额外安装 Qt 开发环境或者依赖一些第三方封装包来获取 Designer。虽然也有人把 Designer 单独装好然后把.ui文件拿回来用pyuic6转换但从零开始配置的体验明显更曲折。我第一次配 PyQt6 的 Designer 环境时光是搞清楚从哪里下载对应版本的工具就花了不少时间而 PySide6 直接省掉了这一步。4.2 绑定生成器与文档生态PyQt6 的 SIP 绑定生成器历史悠久稳定性极高而且能够处理很多 C 的复杂类型这是它的优势。PySide6 的 Shiboken 由 Qt 官方维护会随着每次 Qt 版本发布同步更新对于绝大多数项目两者在性能上的差距微乎其微你可能根本感觉不到。文档生态就有意思了。PyQt6 的官方文档是独立的 HTML 参考内容很全面但风格偏向“查字典”而 PySide6 的文档很多时候会和 Qt 官方的 C 文档直接关联你查一个类的时候能看到 Python 写法和 C 文档的联动。对我个人而言PySide6 的文档体验更舒服因为它有官方团队在做一致性维护版本更新时文档同步度更高。当然这也有反作用PySide6 的文档有时过于依赖 C 术语对纯 Python 开发者不太友好这时你就需要配合 Qt 官方文档和社区例子来理解。4.3 打包成可执行文件的差异打包这一步经常被低估等到真正交付的时候才会遇到问题。我用 PyInstaller 打包过这两个库的程序总体感受是PySide6 的钩子更完整PyInstaller 会主动收集它需要的 Qt 插件和动态库而 PyQt6 在某些环境下需要你手动排除或添加一些模块。一个常见的打包命令是pyinstaller --windowed --onefile app.py如果用 PySide6通常这样就能跑用 PyQt6 时偶尔会遇到程序启动时报could not find or load the Qt platform plugin windows这类错误此时你需要在 spec 文件里手动添加插件路径或者用--collect-all参数把 Qt 相关的文件全部打包进来pyinstaller --windowed --onefile --collect-all PySide6 app.py如果你用的是 PyQt6记得检查是否有缺失的隐藏导入例如PyQt6.QtSvg、PyQt6.QtQml这种冷门模块不主动声明它们不会被自动识别。把开发工具链放在同一个对比表里结论非常明显对比项PyQt6PySide6自带 Designer 工具需额外安装开箱即用UI 转换命令pyuic6pyside6-uic绑定生成器SIPShiboken文档联动性独立文档与官方 C 文档联动PyInstaller 钩子完善度良好更好5. 到底怎么选我的判断标准和避坑清单前面聊了这么多底层差异最后还是要回到“我该怎么选”这个问题上。我的逻辑非常简单一句话就能说清如果你的项目存在闭源分发的可能或者你想要最省心的官方支持和工具链选 PySide6如果你有大批存量 PyQt 代码、开源项目场景或者已经为 PyQt 的商业授权铺好了路继续用 PyQt6 也完全合理。5.1 按项目类型快速决策我通常会把项目分成三类每一类对应不同的选择个人工具、学习项目、学生作业两个都行我更推荐 PySide6理由是需要装的东西最少遇到问题搜官方文档最方便。开源项目、需要 GPL 兼容的开源组件如果你对 GPL 没有排斥PyQt6 的资源丰富度是实打实的加分项许多开源项目也还在使用 PyQt。商业软件、闭源交付、公司内部系统选 PySide6不用纠结商业授权成本和合规审批把精力留到业务逻辑上。这里面有一条最重要别把“先写着以后再说”当默认选项。许可证是不能回头补的东西我见过一些项目前脚用 PyQt6 写完后脚要交付客户突然发现还需要买授权只能花时间改造到 PySide6还留下一些不确定的合规问题得不偿失。5.2 常见问题速查表我收集了十个在选型时最常被问到的问题集中整理成一张表可以直接对照参考问题建议我是纯新手第一次学 Python GUI选哪个PySide6安装即用文档联动性好公司要发布商业软件不想开源选哪个PySide6LGPL 不需要买商业授权我已经写了很多 PyQt5 代码要不要换如果现有代码能正常工作不换等项目重构时再考虑我想用最新的 Qt 特性选哪个PySide6更新节奏更紧跟官方版本我看教程都是 PyQt5 的能跟着学吗可以PyQt5 语法和 PyQt6/PySide6 大体兼容遇到 API 差异时查对应文档性能上两个库有差距吗大部分场景无感知GUI 瓶颈通常在控件数量和业务逻辑打包成 exe 哪个更简单PySide6 的 PyInstaller 钩子更完善两个库可以在一个项目里混用吗强烈不建议它们各自依赖不同的绑定层混用会导致崩溃用 Qt Designer 画界面两个都能用吗都能用生成的.ui文件可以直接相互转换但命令不同未来 Qt 官方会更偏向哪个从当前更新趋势看PySide6 是官方主推方向5.3 实际项目迁移的经验我曾在某跨平台系统里用 PyQt5 做了一个完整版本后来客户对许可证提出要求决定切换到 PySide6。全员执行的时候迁移策略是在项目里加一个compat.py兼容层统一管理导入和重命名映射业务代码尽量不直接接触两个库的专有命名。大致思路是这样的# compat.py try: from PySide6.QtCore import Qt, Signal, Slot from PySide6.QtWidgets import QApplication, QMainWindow, QWidget UI_DIR pyside6-uic except ImportError: from PyQt6.QtCore import Qt, pyqtSignal as Signal, pyqtSlot as Slot from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget然后所有业务模块都从这个compat.py导入避免散落一地的PyQt6或PySide6关键词。这样以后就算再切换一次也只需要维护这一个文件。这个做法的缺点是多了一层导入逻辑调试时看源码不够“直白”但团队协作时能极大降低维护成本。5.4 我最终的个人偏好说得更直白一点我的个人偏好是 PySide6这不是因为它技术上比 PyQt6 强多少而是因为“不用为许可证操心”这件事在项目生命周期里能省掉太多不必要的沟通成本。尤其是独立开发者和外包场景你的第一目标是把功能交付出去而不是跟法务部门反复解释 GPL 的传染边界。我也保留了对 PyQt6 的好感毕竟它的生态历史太有价值了。如果未来某个项目完全开源而且需要大量参考 PyQt5 时代的旧代码PyQt6 会是我重新审视的选项。对于现在的新项目我的默认起点就是 PySide6没有第二个念头。最后分享一个习惯不论选了哪个库都先把最小的窗口程序、一个带信号槽的按钮、一个打开文件对话框的调用跑通再深入业务代码。这样你会在第一时间感受到这个库的手感也能在早期就把那些 import 和枚举层面的坑踩完后面反而越写越顺。选 PyQt6 还是 PySide6 这件事并没有必要上升到“信仰”层次它就是一个围绕许可证、工具链和生态依赖的工程决策。把这三个条件理清楚你的答案早就写在需求清单里了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进