最近在做一个 PyInstaller 打包 + tkinter GUI 的项目,踩了不少坑,整理出来给佬们避雷。
背景
项目结构大致是:GUI 主程序负责界面和交互,动态执行一个打包进去的 Python 脚本,
脚本的 sys.stdout / sys.stderr 重定向到 GUI 的文本框。
看起来简单,但联调过程中几乎每一个环节都出了幺蛾子。
Q1 PermissionError / RuntimeError:无法获取脚本内容
现象:打包后运行报 PermissionError: Permission denied 或
RuntimeError: 无法获取脚本内容。
原因:PyInstaller onefile 模式下,_MEIPASS 是只读内存映射,
用 open() 直接读取其中的文件会失败。
解法:改用嵌入脚本方案,把脚本内容写成一个 Python 模块
(embedded_script.py),在打包时作为普通模块引入,不在运行时读文件。
Q2 退出码 -1,但没有任何错误详情
现象:脚本执行失败,捕获到退出码 -1,日志里没有任何异常信息。
原因:sys.exit() 抛出的是 SystemExit,它继承自 BaseException
而非 Exception,普通的 except Exception 捕获不到,异常被静默丢弃。
解法:
try:
exec(script_code)
except BaseException as e:
if isinstance(e, SystemExit):
error_msg = f"[SystemExit] sys.exit({e.code})"
else:
error_msg = traceback.format_exc()
Q3 logging 报 AttributeError:‘GuiWriter’ 没有 isatty
现象:AttributeError: 'GuiWriter' object has no attribute 'isatty'
原因:logging.StreamHandler 内部会调用 stream.isatty() 判断是否为终端,
自定义的 GuiWriter 没有实现这个方法。
解法:在 GuiWriter 类里补一个:
def isatty(self):
return False
Q4 TypeError:string argument expected, got ‘bytes’
现象:脚本运行时报类型错误,看起来跟字节/字符串混用有关。
原因:脚本里有一段 Windows UTF-8 兼容处理:
if sys.platform == 'win32' and hasattr(sys.stdout, 'buffer'):
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
这段代码把 sys.stdout 包了一层 TextIOWrapper,但外层 GuiWriter
已经接管了 sys.stdout,两者的类型不一致,写入时就炸了。
解法:在 generate_embedded_script.py 中,生成脚本内容时把这段替换掉:
_win_utf8_block = """if sys.platform == 'win32' and hasattr(sys.stdout, 'buffer'):
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
"""
content = content.replace(_win_utf8_block, "")
Q5 batch 脚本报错:‘ansion’ 不是内部或外部命令
现象:PowerShell 执行 batch 文件时,某条命令被截断,
报类似 'ansion' 不是内部或外部命令 的错误(expansion 被截成了 ansion)。
原因:batch 里用了 call set "VAR=%%VAR:--onedir=%%" 做字符串替换,
遇到参数中含有分号 ; 时会截断;加上 PowerShell 对 !VAR! 有特殊解析,
变量扩展结果与预期不一致。
解法:放弃变量拼接,直接在 batch 中写出完整参数列表,不做运行时字符串替换。
Q6 Excel 文件路径 ./exam_info_df.xlsx 找不到
现象:打包后运行,脚本找不到 Excel 文件,但开发环境没问题。
原因:冻结环境下 Path.cwd() 指向 exe 所在目录,不是项目源码目录,
相对路径失效。
解法:区分冻结环境和开发环境,优先从 _MEIPASS 取文件:
import sys
from pathlib import Path
if getattr(sys, 'frozen', False):
base = Path(sys._MEIPASS)
else:
base = Path(__file__).parent
excel_path = base / "exam_info_df.xlsx"
或者通过 CLI 参数 --excel-path 传入绝对路径,更灵活。
Q7 WebDriver 版本不匹配
现象:SessionNotCreatedException,WebDriver 与浏览器版本不兼容。
原因:Edge 浏览器静默自动更新,项目里打包的 msedgedriver.exe 版本落后了。
解法:去官方下载页面下载与当前浏览器版本匹配的驱动,
替换 edgedriver_win64/msedgedriver.exe。
速查表
现象 根因 方案 PermissionError 读脚本_MEIPASS 只读
改用嵌入脚本模块
退出码 -1 无详情
SystemExit 不被 except Exception 捕获
改用 except BaseException
isatty() 属性错误
StreamHandler 调用了 isatty()
GuiWriter 中补实现
stdout 类型冲突
TextIOWrapper 与 GuiWriter 冲突
生成脚本时移除包装代码
batch 命令截断
分号与 !VAR! 的特殊解析
不做变量拼接,直接写完整参数
Excel 路径找不到
cwd() ≠ 项目目录
用 _MEIPASS 或绝对路径参数
WebDriver 不兼容
浏览器自动更新后驱动落后
匹配版本重新下载驱动
踩完这些坑最大的感受是:PyInstaller 打包后,一切"理所当然"的运行时假设都要重新验证——
工作目录、文件读写权限、标准流对象的接口、异常捕获的范围,一个都不能漏。
佬们如果遇到类似问题欢迎评论区交流 ![]()
1 个帖子 - 1 位参与者