ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

PySimpleGUI 4.60.5:最后一个免费版的安装与避坑

PySimpleGUI 4.60.5:最后一个免费版的安装与避坑

简介:PySimpleGUI 4.60.5 免授权安装包,专门解决 pip 默认安装 5.0 收费版及 .exe 打开弹出 30 天试用提示的痛点。它基于 Tkinter、PyQt 等底层框架的统一封装,适合数据可视化、数据录入、系统监控、桌面小工具等场景,开发者可直接调用高层 API 快速搭建窗口、按钮和布局,无需深入了解底层 GUI 细节。整个 RAR 压缩包只有 1.81MB,共 13 个文件,以 5 个 py 源码、4 个 txt 说明为主,另有 setup.py、setup.cfg 与 egg/pkg-info 等配置及元数据,结构紧凑,支持通过 pip 指向本地目录离线安装。目前已有 1391 人学习下载,尤其适合规避版权费用的 Python 爱好者,既可用于个人学习和课后项目,也可嵌入内部工具,避开新版本试用限制。附带简明安装说明,可大幅减少环境搭建时间;同时 4.60.5 版本稳定性高、API 与新版接近,后期若需升级迁移,也较为平滑。

1. PySimpleGUI 4.60.5:最后一个免费老版本,还够你用吗

A同学给某办公室做一款录入小工具,直接pip install pysimplegui装到的版本启动后弹 license,折腾一小时,最后降级到 PySimpleGUI 4.60.5 才收工。这应该是近期 GUI 入门者最容易踩到的一个版本变化:5.0 开始收费,4.60.5 仍是 PyPI 上可下的老版本,而且 API 完全够开发内部工具。这篇照着装就行,同时把验证和锁版本也一并说了。

它能解决的问题很明确:你不想折腾商业授权,又想要一个能用 Tkinter 的图形界面;你希望把版本锁在 4.60.5,让同事 clone 项目后一条命令装完。不适合拿来对新版某个特性做尝鲜,也别指望它切换到 Qt 后端后,4.x 的旧 API 会继续提供所有能力。看完第 3 章你就能把窗口跑起来,第 4 章帮你避开安装阶段最常见的四个坑,第 5 章教你怎么确认没装错,第 6 章给一个写长任务界面时用得上的小技巧。

2. 5.0 收费之后,4.60.5 凭什么还值得装:版本演进与选型理由

2.1 从开源免费到商业订阅:5.0 改了什么

PySimpleGUI 的核心理念是“让你用声明式的二维列表描述窗口,而不是写 Tkinter 的 pack/grid”。在 4.60.5 这批老版本里,一个窗口就是 Python 列表套列表,内层列表对应窗口里的一行,再把 layout 传给sg.Window()。开发体验接近前端写 HTML,但没有浏览器和 node_modules,只要 Python 能跑,它就能跑。

5.0 的收费变化不是把界面 API 全部推翻,而是官方把项目从完全免费转向商业订阅:新版安装后会在启动阶段检查许可证,没有授权就弹提示或直接退出。对内部工具来说,这种依赖就有合规风险,所以把项目固定到 4.60.5,不是为了“省那点钱”,而是为了安装行为和运行行为可预期。更实际的原因,我在某公司给后勤部门做盘点工具时遇到过:所有机器都有 Python 3.9,但没装任何 GUI 依赖。如果直接用 5.0,要么补齐授权,要么就要告诉同事“这个工具会弹收费提示”,体验直接崩。降级到 4.60.5 后,py 文件拷过去、安装一行命令,窗口正常弹出来,后来这件事成了团队内部的标准做法。

在选定版本前,有一个硬前置条件:目标机器必须能运行 Tkinter。建议先执行下面的检查,确认 Python 和 Tk 都正常:

python -c "import sys; print(sys.version)" python -c "import tkinter; tkinter._test()"

第一条用来确认解释器版本。第二条会弹出一个 Tk 自带的小窗口,如果直接报No module named 'tkinter',是 Python 缺少 Tk 支持(Linux 最常见,Windows 大多数已内置)。这个时候装 PySimpleGUI 一定失败或运行时不显示,先解决这个再进入下一步。我第一次在服务器上装完PySimpleGUI后运行窗口,报的就是TclError: no display name,后来才意识到不是包的问题,而是 Linux 环境根本没有图形会话。

2.2 4.60.5 的功能边界:做一个桌面小工具的最低要求

4.60.5 提供的元素数量虽然没有后来新版本那么多,但桌面小工具最常用的部分都在。我列一下自己长期依赖的清单:

  • 表单类:Input、Combo、Checkbox、Radio、Multiline
  • 展示类:Text、Table、Listbox、Image
  • 布局类:Column、Frame、Tab
  • 反馈类:Button、Popup、ProgressBar
  • 辅助类:theme、元素内 padding、window 的 finalize 和 timeout 读事件

这已经覆盖了约 90% 的内部工具场景。比如一个批量改名工具,左边 Listbox 列出文件,中间 Input 输入规则,右边 Button 执行,下面 Multiline 输出结果,全程不碰底层 Tk,也不会涉及 Web 前端。4.60.5 的事件循环只围绕一个方法:window.read()。它返回(event, values),第一个是用户点击控件的 key 或窗口关闭事件,第二个是所有输入控件的值字典。

要判断某个功能在 4.60.5 里是否可用,有一个笨但有效的办法:确认搜索到的资料附带代码里的 PySimpleGUI 版本是否 ≤4.60.5,如果教程只写“新版推荐”,那老版本多半会报 TypeError。这种版本错位比安装失败更隐蔽,第 4 章我会单独说。最后给一张选型对比表:

对比项4.60.55.0
授权费用免费商业订阅
许可证校验无有,启动阶段触发
API 文档年代4.x 资料新版资料
安装命令需要精确指定版本直接 pip 安装会拉到新版
适合场景内部工具 / 个人脚本采购授权的正式项目

我的建议是:没有采购计划的团队,直接按 4.60.5 写;如果已经买授权,用新版就好。别抱着“老版本一定不够用”的顾虑,你的表单、弹窗、事件循环老版本都有。做内部工具最关键的是能交付、能维护,而不是追着许可证换 API。

3. 安装 4.60.5 的完整三条路:pip 指定版本、镜像源与 venv 隔离

3.1 先建 venv:把老版本关进独立环境

为什么先建 venv?最直接的理由:如果你机器上已经装了 5.x,运行pip install --upgrade PySimpleGUI反而升级到更新的 5.x;如果你从来没装过,裸 pip install 默认版本也可能直接装成新版。venv 像一个独立盒子,项目依赖都放在自己的目录里,卸载、重装、锁版本都不会影响到全局系统。

mkdir mygui && cd mygui python3 -m venv venv source venv/bin/activate # 激活虚拟环境,Windows 下是 venv\Scripts\activate

参数说明:

  • python3 -m venv venv:第一个 venv 是模块名,第二个 venv 是环境目录名,可改成任何名称。
  • source venv/bin/activate:进入环境后命令提示符前会出现(venv),这时候 pip 安装的目标就从全局变成本地环境。
  • Windows 下如果python3不存在,用py -3 -m venv venv,激活脚本位置在venv\Scripts\activate.bat。

这一步不一定要做,但我强烈建议做。它能在后面帮你省掉“咦我明明装了怎么还是 ModuleNotFoundError”的血泪时间。如果你用的是 Conda,也可以建一个干净的 Python 3.9 环境,效果是一样的,只是 venv 对纯 pip 流程更轻,不需要额外装包管理器。我一般会在项目根目录把 venv 放在.venv目录下,然后给.gitignore加一行.venv/,这样别人 clone 后自己重建环境,不会把一整套依赖提交到代码库。

3.2 用精确版本号安装:两条最常用的 pip 命令

进入 venv 后,执行:

pip install PySimpleGUI==4.60.5

这行命令的==是明确版本匹配,pip 会去 PyPI 找对应版本号,只要该版本还存在,就能下载。为了兼容国内网络,可以追加镜像源:

pip install PySimpleGUI==4.60.5 -i https://pypi.tuna.tsinghua.edu.cn/simple

-i参数只是更换索引地址,包内容与源无关。我一般不会直接pip install pysimplegui,因为没法确保装到哪个版本;等以后需要升级时再显式决定。如果你的 pip 版本较旧,可以先把 pip 升级到最新:

python -m pip install --upgrade pip

这不是必选项,但旧 pip 解析安装有时会忽略精确匹配或给出奇怪提示,升一下能少踩几个无关坑。另外提醒:包名大小写不敏感,为了统一,我在 requirements 里用PySimpleGUI,和官方大小写保持一致。如果你在安装前已经装过别的版本,我建议先卸载干净再装老版本:

pip uninstall -y PySimpleGUI pip install PySimpleGUI==4.60.5

卸载那一步不是必须的,但能避免两个版本的残留文件叠加。尤其当你之前用源码方式安装过某版,import时可能读的是旧路径,pip 的提示又不会告诉你。卸载后重新安装,路径一定是新写入的,后面排查起来更干净。

3.3 跑通第一个窗口:验证已经装进 4.60.5

安装完成不打开窗口不算数。写一个最小确认脚本:

import PySimpleGUI as sg layout = [ [sg.Text("4.60.5 正常工作了")], [sg.Button("退出")] ] window = sg.Window("安装验证", layout) while True: event, values = window.read() if event == sg.WIN_CLOSED or event == "退出": break window.close()

保存为demo.py,在 venv 里执行:

python demo.py

能弹出窗口,恭喜。逻辑说明:

  • layout是一个二维列表,内层[sg.Text(...), sg.Button(...)]表示窗口同一行中的两个控件。
  • sg.Window(title, layout)创建窗口,但此时还没有显示,真正显示和交互发生在window.read()。
  • read()会阻塞程序,直到用户点击按钮或关闭窗口。返回值里event是触发事件的元素标识,默认是按钮文字;values是输入控件内容字典,这里没有输入控件。
  • 当事件等于sg.WIN_CLOSED时,说明用户点了右上角 X;等于“退出”时说明点击了按钮。两种情况都跳出循环并关闭窗口。

这是 PySimpleGUI 4.60.5 最基本的事件循环写法,后续所有工具都基于这个骨架。虽然values在这个例子里没有被用到,但每个read()都会返回它,这是为了让你随时能拿到输入框、复选框的当前值。如果想把版本号也打到标题上,可以这样改:

sg.theme("DarkBlue") window = sg.Window(f"PySimpleGUI {sg.__version__}", layout)

把sg.__version__拼进窗口标题,一运行就能看到当前导入的是不是 4.60.5。如果这里打印的是 5.x,说明你的环境没锁住,回到第 3.2 节重新指定版本。sg.theme只负责切换配色主题,换成"Default"、"LightGreen"都能跑,不会影响布局逻辑。

4. 装 4.60.5 的四个避坑现场:pip 变成新版、import 不到、TclError、API 不兼容

4.1 现象:pip install 一行命令跑完,实际装成 5.0

原因:很多教程和旧习惯直接执行pip install PySimpleGUI,pip 默认选择该包的最新发布版本。而现在最新版是 5.x,带有商业许可,启动时会弹授权提示。现象就是窗口还没弹出来,终端里先出现 license 相关提示,或者运行后直接退出。

解决:还原到 4.60.5 并锁定。在虚拟环境里执行:

pip uninstall -y PySimpleGUI pip install PySimpleGUI==4.60.5

之后再用pip show PySimpleGUI确认Version: 4.60.5。我自己的习惯是:凡涉及 PySimpleGUI 的项目,requirements 里永不写裸包名,必须带==4.60.5,避免同事在一个月后装上不可用的新版本。你还需要注意一个细节:PyPI 对包名大小写不敏感,但卸载时如果用pip uninstall pysimplegui也可能卸载不到,因为有些平台记录的是原始包名PySimpleGUI。最保险的做法是python -m pip list看输出里的准确名称,再按那个名字卸载。

4.2 现象:pip list 里有 PySimpleGUI,import 却报 ModuleNotFoundError

原因:最常见的是 shell 里敲pip和运行python demo.py用的解释器不是同一个环境。比如在 venv 激活状态下,pip 可能指向全局(个别安装的 pip 脚本问题),或者你在 IDE 里选了另一个解释器。我在 VSCode 里遇到过一次,终端显示的 venv 没问题,但右下角解释器还指向系统 Python,结果python demo.py去的是全局目录,当然找不到包。

解决:统一使用python -m pip,不要用裸pip:

python -m pip install PySimpleGUI==4.60.5 python -c "import PySimpleGUI as sg; print(sg.__version__)"

python -m pip明确指定“当前解释器的 pip”,从根源避开环境混杂。如果还不能 import,执行which python和which pip看路径是否指向同一个 venv。在 Windows 上用where python和where pip。如果 IDE 里已经选了 venv,还要确认python前面没有套一层别的前缀。检查完环境之后,再执行:

python -c "import PySimpleGUI; print(PySimpleGUI.__file__)"

这行会打印包的实际路径,看这个路径是否落在你期望的 venv 目录里。如果落在全局 site-packages,说明当前命令行的解释器就没激活 venv,问题在环境切换而不是包本身。

4.3 现象:窗口闪不出来,报 TclError: no display name

原因:PySimpleGUI 4.60.5 在 Linux/Unix 上依赖 Tk 的 X11 显示环境,如果在纯命令行服务器或没有图形会话的环境里,Tk 无法初始化。错误信息通常长这样:_tkinter.TclError: no display name and no $DISPLAY environment variable。它不代表 PySimpleGUI 没装好,而是当前会话没有可用的图形输出。

解决:要么在带图形界面的机器上运行,要么为服务器加 X11 转发;在 Linux 上还需要确认系统装有python3-tk。缺 Tk 时,import tkinter会报ModuleNotFoundError,安装后仍然可能失败:

sudo apt update sudo apt install -y python3-tk

备注:Windows/macOS 桌面版用户一般不会遇到这个;如果遇到“突然弹不出窗口”,先检查是不是通过 SSH 或无桌面远程连接到了 Linux。还有一种情况是 WSL 环境,WSL 里跑的 Linux 默认没有 GUI 会话,除非你配了 WSLg 或转发了 DISPLAY,否则同样报 TclError。解决办法不是去改代码,而是把程序放到有图形会话的机器上运行,或者临时跑一个 Xvfb 虚拟屏幕,但那种做法更适合自动化测试,不适合给用户真实使用。

4.4 现象:从新教程抄代码,老版本运行报 TypeError / AttributeError

原因:网上很容易搜到 5.0 的新 API 或不再兼容的写法,而 4.60.5 没有这些签名。比如某些控件的新参数、窗口的某种 read 选项,在新版本里存在,在老版本里会直接抛错。即使同一个控件,新版本改了默认值,老版本也需要显式传参,抄代码时不注意就翻车。

解决:写代码时只使用 4.60.5 的老文档接口。判断方法很直观:执行

python -m pip show PySimpleGUI

看到Version: 4.60.5后,凡是看到 “5.0 新功能”、“仅新版本支持” 的代码可以先不用。把布局、事件循环、Popup 这三类内容在本地跑通,基本就能避开 90% 的兼容问题。如果想知道某个方法支持哪些参数,直接查当前包的签名:

python -c "import PySimpleGUI as sg; help(sg.Window)"

help()输出会列出当前版本里Window构造函数能接受的所有参数,比网上搜到的解析来得准确。我通常不会盲抄网上代码,而是先用help()或dir()确认方法存在,再套进自己的布局。如果真的很想用新特性,那只能换到已购买授权的新版环境,不能依赖老版本硬顶上。把老版本代码升级到新版,也不是简单地改版本号,因为 API 签名可能有变化,需要按官方迁移说明走。

5. 验证安装真没踩到收费版本:版本号、license 标记与 requirements 锁定

5.1 用 pip show 和version双重确认

安装完别只看窗口能弹,要确认解释器读到的确实是同一个包。用命令:

python -m pip show PySimpleGUI python -c "import PySimpleGUI as sg; print(sg.__version__)"

第一行里检查Version: 4.60.5,第二行直接输出当前解释器里导入包的版本号。如果两条结果不一致,说明 import 走的路径不对,回到第 4.2 节。如果版本号是 5.x 或出现 license 字样,说明包被升级到了新版或存在多个版本残留,需要卸载后重装。注意:有些版本的包可能没有暴露__version__,那以pip show为准;如果pip show显示的版本没问题但__version__报错,你照样可以用,只是这个包没有提供该属性,不影响功能。

5.2 用 requirements.txt 锁死版本,随手可以交付

项目根目录建requirements.txt,内容写好:

PySimpleGUI==4.60.5

然后在 venv 内执行:

pip install -r requirements.txt

这样不管同事什么时候拉代码,安装的都是同一个版本。为了生成更完整的依赖清单,还可以:

pip freeze > requirements.txt

但pip freeze会把所有依赖都写进去,如果项目很干净也可以;如果只需要 PySimpleGUI 一个依赖,手写一行更清晰。我一般这样区分:供自己快速搭建的环境用手写精简清单;要可复现一套完整体验环境才用pip freeze。requirements 文件本身就是安装说明书,比口头说“帮我装老版本”可靠得多。新人拿到后执行python -m venv venv、激活、pip install -r requirements.txt,三分钟就能把环境搭起来。

配合 requirements 还可以加一个README.md片段,写清楚“为什么锁 4.60.5”,避免三个月后有人把版本号改成新版,然后又陷入 license 提示。这种文档内容不需要长,两三句就行,重点是把版本原因讲明白,而不是简单地写死版本号。我见过太多因为版本号被随手升级而导致的兼容事故,锁版本不是给别人添麻烦,是给自己省沟通成本。

5.3 老版本运行时的三个验证点:窗口、事件、输入值

为了避免把“能弹窗”当成“完全可用”,需要再跑一个带输入控件的验证脚本:

import PySimpleGUI as sg layout = [ [sg.Text("输入内容")], [sg.Input(key="-TEXT-")], [sg.Button("读取")], ] window = sg.Window("验证", layout) while True: event, values = window.read() if event == sg.WIN_CLOSED: break if event == "读取": sg.popup("你输入了:", values["-TEXT-"]) break window.close()

这个脚本验证三件事:

  • 窗口能否正常创建并显示:window = sg.Window(...)
  • 事件能不能被读出来:按钮点击后event等于按钮文字
  • 输入值能不能从values里取到:通过key标识-TEXT-,不依赖控件列表下标

这三点是 PySimpleGUI 4.60.5 所有小工具的地基,跑通后基本可以放心开发。如果要交给不熟悉 Python 的同事,可以把脚本改名成app.py,让他们只用python app.py启动。用 key 而不是下标来取输入值是一个好习惯,因为布局里增删控件时,控件的列表位置会变,而下标值不会跟着语义走。比如你在第一行加了一个说明文本,输入框从values[0]变成了values[1],一旦忘记改取用代码,数据就错位了。用-TEXT-这种具名 key,布局怎么调整都不会影响取用。

6. 把 4.60.5 用透的最后一个技巧:用 timeout 循环避免长任务卡死窗口

很多新手在按钮事件里直接写耗时操作,比如读取大文件、请求接口、跑批量计算,结果窗口卡成“无响应”。这是因为window.read()在等待事件时,同一线程必须处理 UI 消息;如果事件处理函数里塞了个time.sleep(5),整个窗口就冻结了。4.60.5 里没有新版那套异步脚手架,但可以用window.read(timeout=100)做定时轮询,再配合线程把结果传回来。

import queue import threading import time import PySimpleGUI as sg q = queue.Queue() def long_task(text): time.sleep(3) # 模拟耗时操作 q.put("完成: " + text) layout = [ [sg.Input(key="-IN-")], [sg.Button("开始")], [sg.Text(key="-OUT-", size=(40, 1))] ] window = sg.Window("长任务", layout) while True: event, values = window.read(timeout=100) # 最多阻塞 100ms if event == sg.WIN_CLOSED: break if event == "开始": threading.Thread( target=long_task, args=(values["-IN-"],), daemon=True ).start() try: while True: result = q.get_nowait() window["-OUT-"].update(result) except queue.Empty: pass window.refresh() window.close()

这段代码的关键有两点:timeout=100让read()每 100ms 返回一次,哪怕没有任何事件;线程完成long_task后把结果放进queue,主循环每次都去队列里取,取到就更新-OUT-文本。这样窗口在等待期间仍然能正常响应用户操作,不会像睡死在事件函数里一样。

我在实际项目里用这个模板写过一个小型查询工具,左边输入订单号,右边点查询,后台线程去查数据库,查完后把结果写进页面。整个过程界面始终能拖动、点按钮,用户不会以为程序死了。后来这个模板被我固化进所有老版本 GUI 项目里,只要是耗时操作,一律不让它碰主事件循环。如果你用的是 Windows,time.sleep换成真正的 IO 操作也同理。耗时不长但需要频繁刷新的,可以把timeout调小到 50ms;如果耗时很长,可以调大到 200ms 减少 CPU 占用。这算是我在 4.60.5 上最值得分享的一个技巧,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

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