auto py to exe,基于PyInstaller的图形界面工具
昨天晚上,快下班的时候,产品又来一句经典台词: “东哥,你那个小工具能不能给我们弄成一个双击就能用的?别让我再装什么派森(他每次都念派森)了。”
我人都麻了,你们知道吧,这话十年如一日。之前写数据库压测脚本的时候,也是被人追着问要“绿色版”,跟我说“就像你上次那个Postgres文章里那种工具一样,一解压就能跑”
我当时电脑已经关到一半了,又不好意思说不行,只能一边骂骂咧咧把盖子掀开,一边心里默念: “还好有 auto-py-to-exe,不然今天是真的走不了。”
你要是直接上 PyInstaller 命令行那套,长这样:
pyinstaller -F -w -i icon.ico main.py --add-data "config.yml;."
别说产品,就连我自己隔俩月都得查一眼文档,参数一多就开始眼神涣散。 auto-py-to-exe 这玩意儿,说白了就是给 PyInstaller 套了个壳,给你搞了个图形界面,大部分选项用点鼠标就完事了,适合我这种下班后脑子已经关机的生物。
那天的需求也简单,就是一个帮测试妹子批量改文件名的小脚本,Python 写出来也就二十行,核心逻辑差不多这样:
import os
from pathlib import Pathdefbatch_rename(folder: str, prefix: str):
base = Path(folder)
for idx, f in enumerate(base.iterdir(), start=1):
ifnot f.is_file():
continue
new_name = f"{prefix}_{idx}{f.suffix}"
old_path = f
new_path = base / new_name
print(f"rename {old_path.name} -> {new_name}")
old_path.rename(new_path)
if __name__ == "__main__":
# 这里就偷个懒,真实环境你可以改成读配置或者简单 GUI
target = input("输入要处理的文件夹路径:").strip()
pre = input("输入文件前缀:").strip()
batch_rename(target, pre)
input("完成啦,按回车退出...")
就这么个小东西,给产品说明白都比写代码费劲。 但是——只要你说“发你一个 exe,解压就能双击用”,他们立马感觉这个工具值 998。
我就打开命令行敲了一句:
auto-py-to-exe
这个地方每次都要吐槽一下,命令名还挺长的,语音识别老是给我识别成 “auto pie to exe”,听着就像在做苹果派。
窗口起来之后,流程基本就是: 先点 Browse 把刚才那个 rename_tool.py 选进去, 然后勾一下 Onefile(打包成单文件,不然你发给别人一坨目录他能把依赖删秃噜皮), 再选 Console Based,因为这个脚本本来就是命令行交互,有黑框才有灵魂。
真正容易翻车的其实不是这些按钮,而是“额外文件”那块。 你们有没有遇到过,就是本地运行好好的,一打包以后,exe 一运行就骂你:
FileNotFoundError: config.yml not found
之前我写 SpringBoot 默认配置那篇的时候就说过,默认这两个字,基本等于“埋雷” PyInstaller 也是一个德性:代码里相对路径写得很开心,打包完发现压根不是那个工作目录了。
如果你有类似这种结构:
project/
rename_tool.py
config/
rules.json
代码里可能写的是:
from pathlib import PathBASE_DIR = Path(__file__).parent
RULE_FILE = BASE_DIR / "config" / "rules.json"
本地没问题,一打包成 exe,__file__ 那一套就有点玄学了。 简单粗暴一点的写法,一般我会这么兼容一下:
import os, sys
from pathlib import Pathdefget_base_dir() -> Path:
if hasattr(sys, "_MEIPASS"):
# PyInstaller 解压后的临时目录
return Path(sys._MEIPASS)
return Path(__file__).parent
BASE_DIR = get_base_dir()
RULE_FILE = BASE_DIR / "config" / "rules.json"
然后在 auto-py-to-exe 里,“Additional Files” 那块加一条: 左边选 config 这个文件夹,右边填 config, 翻译成人话就是: “打包的时候把本地的 config 整个塞进去,运行时也叫 config”。
这一步你要是忘了,基本就是经典复现: “在我电脑上能跑,在你电脑上就不行”。
还有个小细节,界面最下面不是有一大串命令行吗? 我一般都会点一下“Copy Command”,存到一个 build_cmd.sh 里, 万一以后你要自动打包,或者搞个 CI,每次让机器自己执行这条命令就完事了, 人不用点来点去,少很多手滑的概率。 这点感觉有点像我之前那个 TCP 1024 字节卡 bug 的故事,一点没对齐就能给你玩出血案
那天打包完,我先自己双击跑了一下, 产品凑过来,眼睛放光:“哇还有黑窗口,好专业啊!” 我心里想:你要是看过源码就不会这么说了,全是 print。
不过 auto-py-to-exe 也不是万能药,有几个坑我踩得比较典型,顺嘴说一下:
一个是第三方库那种“动态 import 的”,比如你用了个插件系统, 代码里 __import__(plugin_name) 这种骚操作, PyInstaller 很可能分析不出来依赖,跑出来直接报 ModuleNotFoundError。 这种情况在 auto-py-to-exe 里就得手动去 “Advanced -> Hidden Imports” 那栏加上模块名, 什么 my_project.plugins.image, my_project.plugins.video 全填进去。
另一个是体积问题。 你打包成单文件 exe,里面把解释器、依赖全塞进去了, 哪怕你脚本只有二十行,打完也可能六七十兆。 这时候别自己嫌弃, 你可以很诚恳地跟产品说一句: “这里面有完整运行时环境,跟你装了一个小型 Python 差不多。” 听起来就很贵。
还有人问我“能不能把多个脚本打成一个 exe,里面点菜单选功能那种”。 这个我之前给测试做过一个丑到爆的版本,大概是这么个结构:
deffeature_rename():
print("这是重命名功能")
# ...deffeature_clean():
print("这是清理日志功能")
# ...
defmain():
whileTrue:
print("1. 批量重命名")
print("2. 清理日志")
print("0. 退出")
choice = input("选一个:").strip()
if choice == "1":
feature_rename()
elif choice == "2":
feature_clean()
elif choice == "0":
break
else:
print("别乱按...")
if __name__ == "__main__":
main()
然后就只打包这一个 toolbox.py, 对外宣传成“运维小工具合集 v1.0”, 他们点一个数字就能用一个功能,比发十个 exe 友好多了。
说真的,auto-py-to-exe 这种东西,你第一次看会觉得就那样, 但真到某个下班前的晚上, 你会发现它是唯一站在你和“远程给同事装 Python、配置环境变量”之间的最后一道防线。
行了我先说到这,我这边又有人在问“图标怎么换成我们公司 logo”, 等我把他劝住,再给你们聊聊图标那点破事儿,不然他一会儿要让我做启动动画了…