个人尝试各 Python GUI 框架及缺点枚举

  1. 文章包含个人倾向, 内容仅供参考, 请谨慎阅读
  2. 本文一共分为上下两篇, 上篇是 Python GUI 框架 (tkinter, PyQt, wxPython, etc.), 下篇是 Python Web 框架 + 前端技术实现 (Flask/Django/etc. + Electron/Remi/sofi/Carlo/etc.)
  3. 各小节按照 推荐度 降序排列

上篇: Python GUI 框架

PyQt5 / PySide2 (★★★★)

特性

  1. 使用 QML 可以轻松制作组件动画
    在这里插入图片描述 在这里插入图片描述

QML 问题

  1. QML 的 implicitWidth/implicitHeight, anchors & alignment 以及 AbstractListModel/AbstractTableModel 是个人认为比较大的难点, 而且出现问题时难以排查定位, 我的大部分时间都用在修复这些错误上
    1. 对于有 background 属性的对象, 比如 Button, 我们会迷惑究竟是在 Button 中定义宽高, 还是在 background 对象中定义宽高; Qt 助手上说应该在 Button 中定义, 但是我在 RowLayout 中对 Button 对齐时遇到了 “怎么都对不齐” 的诡异现象, 后来在 background 中定义隐式宽高解决了
    2. QML 的表格组件比较难用, 特别通过 Python 创建 AbstractTableModel 再绑到表格组件上 (因为我想把数据处理的逻辑放在 Python 中写), 我花了几天的时间还没有搞定
  2. 在 QML 中, 假设我们打印 item.chilren.length 显示 3 个子元素, 但同样的方法在 Python 中打印: len(item.children()), 会发现变成了 4 个! 而且这多出来的一个在位置上是 “随机” 的, 究竟是谁也不得而知 (似乎是一个不可见的对象, 因为打印它的各项属性的值都是 None)
  3. 父组件如果是圆角矩形, 则圆角区域的 clip 属性会失效 (如下图所示)
    在这里插入图片描述
  4. 无法单一地对矩形的其中一个或几个角设置弧度
  5. QML 在导入模块命名空间时, 必须写版本号, 对于初学者来说, 有些库的版本号需要背记下来, 听起来有点荒唐. 我没找到版本号的具体规律是什么, 当然常用的模块的版本号是随着 Qt 发布的次版本号一同增长的, 比如 Qt 5.14 是 QtQuick 的 2.14, Qt 的 5.15 是 QtQuick 的 2.15, 也有某些模块是不遵循的, 只能查 Qt 助手了解 (好消息是听说 Qt 6.0 不用再写版本号了)
  6. QML 中支持 JavaScript, 但一些新语法尚不支持, 比如 for of
  7. 我在一次 Popup 窗口弹出动画中, 同时做了位移和尺寸变换动画, 发现居中的文字出现了较明显的 “颤动” 现象, 如下图所示
    TODO
  8. 假设 ListView 中子组件是 CheckBox (勾选框), 在视图中滚动 ListView, 会发现滚出视界的 CheckBox 在重新回到视界时, 其勾选状态有时候会丢失

Python 与 QML 组件通讯

  1. Python 调用 QML 对象的方法不太方便, 特别的, Python 修改 border.width, border.color, font.family 等二级属性时, QObject.setProperty() 方法无效, 只能通过 QQmlProperty 来实现 (后者写起来比前者繁琐)
  2. 在 Python 中, 无法修改 QML 对象的 anchors 属性

QSS

  1. QSS 无法定义 transitions 等动画相关的属性, 所以无法在 QSS 中定义组件的动画

PS: PyQt5 和 PySide2 的区别

除了网上常说的协议上的区别外, 我这里补充一些二者在具体用法上的区别 (主要根据我使用 PySide2 的经验):

  1. PyQt5 的信号与槽导入的名称是 pyqtSignal, pyqtSlot; PySide2 导入的名称是 Signal, Slot
  2. PyQt5 中的 QVariant 在 PySide2 中被移除了, 因此槽装饰器 @pyqtSlot(QObject, result=QVariant) 应写为 @Slot(QObject, result='QVariant') (后者有点违和感)
  3. PyQt5 和 PySide2 都可以调用 QML 中定义的函数, 但是调用方法都比较隐晦 (平时也很少用到); 另外, PyQt5 可以调用 QML 的含参函数, 但 PySide2 不能
  4. PySimpleGUIQt 是基于 PySide2 做了完整性测试, 作者说向 PyQt5 绑定时遇到了很多困难, 所以不能保证已安装 PyQt5 的用户在使用 PySimpleGUIQt 时是否有异常
  5. enaml 是基于 PyQt5 做了完整性测试的, 作者介绍说 QtPy 模块可以让 PyQt4, PyQt5 和 PySide2 统一调用方法, 写法上统一为 PySide2 那样子 (这是否说明这些作者更偏向于 PySide2?)

PySimpleGUI (★★★★)

简介

如果不考虑复杂的界面动效, PySimpleGUI 绝对是 Python 开发可视化界面的神器, 非常适合 短平快 的客户端编写.

在评测期间, 我对手中的几个项目都做了基于 PySimpleGUI 和 PySide2 的客户端, 个人感受是, PySimpleGUI 写界面的速度非常快, 几小时能写好的界面, 在 PySide2 中可能需要一两天的时间. 而且 PySimpleGUI 的界面外观虽然不够酷炫, 但至少看起来不难看. 这也是我推荐它的一大原因.

缺点

  1. 编辑框没有 text hint 属性 (妥协: 用 default_texttooltip 代替)
  2. 编辑框没有内间距属性 (其 pad 参数相当于 web 的 margin, 真正的 padding 属性不存在)
  3. tooltip 有时会出现提示文字 “抽搐” 的现象

Kivy (★★★☆)

问题1: TextInput 不支持文字居中显示

Stack Overflow 给出的回答解决了 text 的居中问题, 但没有解决 hint_text 无法居中, 我自己花了一些功夫造轮子解决了.

这件事给我带来的忧虑不是说官方为什么没有提供原生的方法, 而是当我寻找答案时, 网上对此的讨论出奇的少 (中文网站完全没有), 而且也没有完美地满足我的需求.

Kivy 的讨论度太低了, 注定后面的大部分的坑需要个人去踩, 这是我真正担忧的地方.

问题2: 自定义组件的引用问题

假设我的项目结构如下:

myproj
|-addressbar.kv
|-addressbar.py
|-home.kv
|-home.py

addressbar.py 中已经定义好了一个 AddressBar. 现在我想要 home.kv 中引用这个自定义组件, 该如何引用?

这个问题我没能解决. 我搜索网上的答案, 以及看了一些官方提供的 demo, 只得到了以下知识:

  1. 在单 kv 文件中定义所有组件 (包括自定义组件) 是可行的, 但如何导入其他 kv 文件的组件不知道
  2. 当我有大量组件分布在不同分文件中, 需要连接布局在一起时, 似乎只能在 py 中实现, 这与我理解的 “视图逻辑相分离” 思想有很大的差别. 因为我想单纯依靠 kv 文件完成所有布局活动

问题3: API 文档不好用

我喜欢 Kivy 将 API 独立为文档的做法, 但文档的内容写得无法让人满意.

当我想要知道 TextInput 有哪些属性可使用时, 我根本无法在 API 文档中弄清楚. 例如:

  • TextInput 是否有 height 属性?
  • TextInput 的排列方式是由 “align” 控制的吗? 以及如果要居中显示, 应该用 “center” 还是 “middle”? (PS: 答案是 “middle”, 但我并非通过 API 找到, 而是通过一些 demo 源码)

关于什么样的 API 才是好的 API 文档, 我建议了解一下 Adobe ExtendScript Toolkit 附带的 Object Module 文档, 虽然看起来很粗糙, 但它支持了我从零到完整开发一个脚本应用的整个过程. 我认为规范的格式和完整的属性列表是构造这类文档的基础与核心.

问题4: 默认不支持中文显示

当创建并显示一个 TextInput 控件时, 往里面输入中文, 会变成方块乱码.

关于解决方法是能够找到的 (比如 这篇文章), 但我不满意的地方是官方没有给出解决方式. 这也意味着官方对中文社区的支持力度还不够.

问题5: 缺少中文教程

我搜集了两个中译版的教程, 这两个教程都是对官方 Getting Started 的译文.

考虑到官方的 API 文档还没有翻译 (加上 API 内容写得也不够好), 虽然我希望有更多的资料支持我的开发, 但目前的结论是长期来看也只能啃生肉了.

enaml (★★★☆)

简介

enaml 是我在寻找 “能够在 QML 中写 Python” 时遇见的, 当然 enaml 自身能力远不止于此…TODO

问题1: 参考资料太少

除了官方文档给出的示例可供参考外, 网上鲜少有关于它的介绍和讨论.

问题2: 在 Style 中设置编辑框的文字对齐方式无效

# === test_view.enaml ===
from enaml.widgets.api import Field
from enaml.styling import StyleSheet, Style, Setter


enamldef MyField1(Field):  # 编辑框
    text = 'hello world'
    StyleSheet:
        Style:
            Setter:  # 我想让编辑框中的文字居中出现
                field = 'text-align'  # 这是文档中列出的受支持的字段
                value = 'center'  # 但是向这个字段赋值, 却是无效的


enamldef MyField2(Field):  # 编辑框
    text = 'hello world'
    text_align = 'center'  # 在属性中定义, 才有效

问题3: 暂不支持 Python 3.9

原因是 Python 3.9 似乎移除了内置的 bytecode/inst.py 模块的 ‘END_FINALLY’ 标志, 导致 enaml 启动时报错.

具体请参考: https://docs.python.org/3/whatsnew/3.8.html#cpython-bytecode-changes

Tkinter

体验时间: 2019年4月26日

问题1: 没有内置的表格组件

关于这个问题, 我通过 这篇文章 找到了一个对 Tkinter 的封装实现方案: tktable, 不过作者早在两年前就停止更新了. 我把源码下载下来, 运行后直接报错:

在这里插入图片描述

报错内容: _tkinter.TclError: invalid command name "table"

看了下报错源码位置, 好像是说 tktable 未安装导致报错, 不过实际上 tktable 不就是它自己吗? 没想明白, 暂时就放弃了.

问题2: 反直觉的列表元素类型

"""
来自: Listbox 列表部件 - 窗口 Tkinter | 莫烦Python - https://morvanzhou.github.io/tutorials/python-basic/tkinter/2-03-listbox/
"""

import tkinter


x = tkinter.StringVar()
x.set((1, 2, 3, 4))
# why set an array of int to a "StringVar"?

问题3: Listbox 不支持支持传入列表 (同样有违直觉)

在这里插入图片描述

报错内容: AttributeError: 'list' object has no attribute 'items'

appJar (★)

TODO

Atlas (☆)

问题1: 网络延迟

Atlas 运行后需要保持网络链接, 国内的连接不稳定, 导致任何交互动作的延迟都非常夸张.

例如官方的 hello world 示例, 点击下图的 Submit 或 Clear (清空文本框内容) 按钮时, 需要等待十多秒浏览器才会弹出对话框.

下篇: Python Web 框架与前端技术结合

Flask + Miniblink

特性 (Miniblink)

  • 基于 chromium 最新版内核, 去除了所有多余的部件, 只保留最基本的排版引擎
  • 内核体积极小 (~20mb), 且拥有完整的网页渲染功能
  • 持续活跃的更新 (截止本文发表的最近更新于 2019.10.15)

注意事项: Miniblink 使用 Apache License 2.0 协议, 使用者需在项目发布文件中显式申明使用了 Miniblink.

问题1: 加载 node.dll 失败

# ./test.py
import ctypes
# 事先将 miniblink 的 node.dll 放在和 test.py 同一目录下
ctypes.cdll.LoadLibrary("node.dll")
# -> 报错: "OSError: [WinError 193] %1 不是有效的 Win32 应用程序"

根据 这个回答, 原因似乎是 64bit Python 运行 32bit DLL 的冲突引起. 本人没有继续进行测试.

参考

  • https://github.com/ynyyn/Miniblink-Python-SimpleDemo

wuy

TODO

flaskwebgui

问题1: 进程管理问题

flaskwebgui 的进程管理似乎有问题, 会导致启动后 CPU 就被拉满. 并且 kill_servers() 方法也是无效的.

参考

Electron + Flask

体验时间

2019年3月 - 2019年5月.

问题1: Flask 默认实例化行为带来的项目结构管理的困扰

Flask 的相对路径有很多坑.

在这里插入图片描述

上图是我摸索得到的经验 (相关文章见 这里), 当我了解了 template_folder, static_folder, static_url_path 这三个参数该如何自定义后, 才摆脱了布局和资源路径找不到的困扰.

问题2: 糟糕的跳转体验

当我在输入框输入一个新路径并按下回车键, 如何更新本页面的列表元素的文件列表信息?

我需要在 <script> 中定位到这个输入框, 获取这个输入框的值, 把值利用 ajax 或者 window.location.href 激发一个 url 请求, 并携带该值作为参数, 等 Flask 解析携带的参数后, 再重定向到原页面, 但与此同时, 也要给原页面交递这个参数.

这个过程不但繁琐, 而且混入了 JS, jQuery 中的各种概念, 让一个基于 Python 的后端不再纯粹, 另外我也不知道它该如何实现局部的渲染, 当我尝试使用 Vue (这又是一个问题) 来局部渲染时, 新的路由问题和模块引用问题让整个逻辑变得更加混乱.

问题3: 缺乏资料

这个问题伴随着我尝试 Flask 的整个过程, 实践示例, 教程和书籍匮乏, 以及很多自己想问的问题找不到答案, 很多错误都是自己摸索, 尝试几天后自己总结的. 即便如此, 现在手上还有一些问题亟待解决, 但也仍未找到解决的线索, 让人感到非常挫败.

Flexx

项目地址: https://github.com/flexxui/flexx

体验时间

2019年3月24日.

问题1: IOLoop 错误

当我测试以下示例时, 运行报错:

from flexx import flx


class AAA(flx.Widget):

	def __init__(self, *init_args, **kwargs):
        super().__init__(*init_args, **kwargs)


flx.launch(AAA)

报错信息为 “type object ‘IOLoop’ has no attribute ‘_current’”.

经查找发现是 flx 使用了 tornado 模块的 IOLoop._current, 而事实上在最新版的 tornado 模块中已经没有 _current, 而是用 IOLoop.current() 取代.

也就是说我们在 flx.launch(AAA) 前加一行 IOLoop._current = IOLoop.current() 才能解决这个低级报错:

在这里插入图片描述

说实话 Flexx 官方没有解决这个问题, 可能是因为在 tornado 模块更新后没有去适配, 也可能是社区不活跃没有人反应问题, 也可能是已经停止维护更新了.

这是我不推荐使用 Flexx 的原因.

flybywire

简介

项目地址: https://github.com/thomasantony/flybywire

在了解 flybywire 之前, 不妨先了解一下 Sofi.

flybywire 的作者深受 Sofi 的启发, 但认为 Sofi 在 UI 的架构理念上显得有些过时 (或者说不够优雅?).

flybywire 的作者同时也深受 React 语言的影响, 认为响应式设计是一种深刻的变革 (关于响应式的理解可以见 这篇文章).

因此, flybywire 在承袭 Sofi 核心思想的同时, 将响应式设计加入到 UI 的渲染方案当中. 如果有了解过 MVVM (比如接触过 Vue), 相信你也会对 flybywire 的做法感兴趣:

在这里插入图片描述

问题1: pip 安装的库是过时的

通过 pip install flybywire 安装的库是过时的, 且无法运行.

请从 git 上克隆他的项目下来, 例如我在我的项目路径下使用 git clone https://github.com/thomasantony/flybywire.git:

在这里插入图片描述

myprj/
|-flybywire/
	|-examples/
	|-flybywire/  # <- 将这个文件夹移动到 venv/Lib/site-packages/ 中
	|-test/
	|-.gitignore
	|-.travis.yml
	|-AUTHORS
	|-ChangeLog
	|-LICENSE
	|-README.md
	|-setup.cfg
	|-setup.py
|-myapp/
|-venv/
	|-Lib/
		|-site-packages/

以及还需要安装一个 autobahn (这个可以 pip 安装):

pip install autobahn

问题2: 无法运行官方示例

是的, 当你安装成功后, 会发现启动后前端页面一直显示加载中… 暂不清楚是安装问题还是代码问题.

在这里插入图片描述

问题3: 没有教程文档

和 Sofi 一样, flybywire 没有教程文档, 这意味着新手很难上手这个框架.

CEFPython (cefpython3) (★★)

TODO

Carlo (★★)

项目地址: https://github.com/GoogleChromeLabs/carlo

TODO

Sofi

项目地址: https://github.com/tryexceptpass/sofi

介绍:

Sofi 的作者尝试将前端 HTML 和后端 Python 相结合, 利用 Websockets 的魔法让 HTML 元素向 Python 传讯以及 Python 操作 DOM 成为现实.

另外的, Sofi 内置了 Bootstrap 的渲染能力, 使默认绘制的前端控件外观现代和前卫. 从 Demo 中可以看到确实让人感到耳目一新, 有别于以往的 Python GUI 项目演示截图中复古的印象.

体验时间: 2019年5月9日

问题1: 性能表现似乎不太好

如图所示, 我在工作电脑上运行官方示例, 在连接到浏览器后, 会有一个明显的 HTML 加载延迟 (注: 在录制该动图时, 该延迟没有被捕捉到, 但在之前的几次运行中都有出现).

在这里插入图片描述

我不知道是这个官方示例写得不够好, 导致表现不佳, 还是电脑的问题 (不过自认为基本办公方面是没问题的). 评估 Sofi 的表现还需要更多的时间了解它.

问题2: 没有教程文档

Sofi 的 tutorials 文件还是空白的, 我估计他还没有写好文档, 但距今已经一年之久了, 不知道什么原因停止更新, 个人感觉有很大的可能是不会有下文了.

意思就是除了两个官方的 demo, 基本找不到可供参考的文档了.

Tornado (利用 Tornado Websocket 实现 Python 与前端通讯)

问题1: 由于继承了 WebSocketHandler 的自定义类无法实例化, 导致自定义类之间通讯受阻

假设我有一个 class AddressBar (地址栏) 和一个 class Filelist (文件列表), 这两个 class 都继承自 tornado.websocket.WebSocketHandler.

现在我想让地址栏在收到前端组件更新时, 随即调用 Filelist 中的 update_filelist() 方法…

TODO


附录

个人建议

根据个人使用的 Python GUI 框架 (很多都是浅尝辄止), 有以下经验可供参考:

  • 尽量使用大公司的, 知名度高的. 大厂的 api docs 非常完善, 一般问题也能快速解决. 而个人开发者做的 sideproject, 有很多都停止更新了, 绝大多数缺乏教程文档, 遇到问题没人解答, 如果你的目的不是给一个单纯的小工具加一个 GUI, 不建议长期去依赖后者

了解一下 Python TUI?

TODO

参考

  • Electron
    • 有开发者抱怨 Electron 占用内存过多等缺点 https://www.reddit.com/r/node/comments/8rhwz7/making_a_very_small_application_is_electronnwjs/
  • PyQt, PySide
    • 这篇文章介绍了 PyQt 和 Pyside 的区别: https://www.e-education.psu.edu/geog489/node/2225
    • Stack Overflow 上关于 PyQt 和 Pyside 差异的回答: https://stackoverflow.com/questions/6888750/pyqt-or-pyside-which-one-to-use
    • Qt 官方对其区别的解释: https://wiki.qt.io/Differences_Between_PySide_and_PyQt
  • Sofi & flybywire
    • Sofi 的作者深刻体会到 Python 开发 GUI 方面的掣肘 (A Python Ate My GUI Series), 这促使他开发了 Sofi https://medium.com/@tryexceptpass/a-python-ate-my-gui-part-3-implementation-39fc105b6d81
    • flybywire 的作者表示自己曾阅读过 Sofi 作者的文章, 对 Sofi 的理念表示赞赏, 但是认为 Sofi 的 UI 的架构理念可能有些过时 https://medium.com/@tantony/flybywire-declarative-guis-for-python-inspired-by-react-ad2131d4cbc1
  • GUI 概论
    • 关于 GUI 的十年架构演化之路 https://zhuanlan.zhihu.com/p/26799645
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值