
PyQt6 项目开发:虚拟环境修复与 PyInstaller 打包常见问题解决
解决了 PyQt6 程序运行时闪退及打包后找不到模块的问题。主要步骤包括:检查并重建被破坏的虚拟环境(.venv),修复 QTextCursor 兼容性问题;使用 PyInstaller 打包时添加 --collect-all 和 --add-data 参数确保资源文件正确包含;处理图标显示问题,区分 exe 文件图标与窗口图标。通过清理构建目录、指定正确的解释器路径及资源路径,最终实现程序正常运行。

解决了 PyQt6 程序运行时闪退及打包后找不到模块的问题。主要步骤包括:检查并重建被破坏的虚拟环境(.venv),修复 QTextCursor 兼容性问题;使用 PyInstaller 打包时添加 --collect-all 和 --add-data 参数确保资源文件正确包含;处理图标显示问题,区分 exe 文件图标与窗口图标。通过清理构建目录、指定正确的解释器路径及资源路径,最终实现程序正常运行。


运行程序后,窗口如图;但是闪退。报错如下。C:\Users\CQJY\Desktop\PythonProject.venv\Scripts\python.exe C:\Users\CQJY\Desktop\PythonProject\main.py 进程已结束,退出代码为 -1073740791 (0xC0000409)

现在这个新报错:
程序'python.exe'无法运行:指定的可执行文件不是此操作系统平台的有效应用程序。
几乎可以肯定是 .venv\Scripts\python.exe 被破坏/替换(或和基准 Python 不一致)。之前它还能跑、能弹'Qt ok',说明不是系统位数问题,而是后来 venv 里的 python.exe 损坏了(最常见是误用 Set-Content、杀毒拦截/隔离、或复制覆盖)。
按下面做,最快恢复:
python.exe 是否异常# 在项目根 cd C:\Users\CQJY\Desktop\PythonProject # 看大小与版本信息
(Get-Item .\.venv\Scripts\python.exe).Length
(Get-Item .\.venv\Scripts\python.exe).VersionInfo | fl ProductVersion,FileVersion
# 与基准 Python 对比(应接近相同)
(Get-Item "C:\Users\CQJY\AppData\Local\Programs\Python\Python313\python.exe").Length
如果 venv 里的体积很小/为 0 或与基准差异巨大,就是被破坏了。
.venv(最稳妥)# 关掉 PyCharm,避免占用 .venv
cd C:\Users\CQJY\Desktop\PythonProject
Remove-Item -Recurse -Force .\.venv
# 用你已安装的 3.13/64 重建
py -3.13 -m venv .venv
# 快速自检
.\.venv\Scripts\python.exe -V
.\.venv\Scripts\python.exe -c "import struct,platform;print('arch=',struct.calcsize('P')*8,'os=',platform.platform())"
# 重新装依赖
.\.venv\Scripts\python.exe -m pip install -U pip
.\.venv\Scripts\python.exe -m pip install PyQt6 pyserial # 串口建议用 pyserial,更稳
⚠️ 小心别再对可执行文件用
Set-Content、Out-File等命令;这些会把二进制覆盖成文本,立刻变成'不是有效应用程序'。
$probe = @'
from PyQt6.QtWidgets import QApplication, QWidget
import sys
app = QApplication(sys.argv)
w = QWidget(); w.setWindowTitle("probe"); w.show()
print("Qt ok")
sys.exit(app.exec())
'@
Set-Content -Path .\_qt_probe.py -Value $probe -Encoding UTF8
.\.venv\Scripts\python.exe .\_qt_probe.py
你之前的异常在:
ui\widgets.py:177 -> self.moveCursor(self.textCursor().End)
改为:
from PyQt6.QtGui import QTextCursor
self.moveCursor(QTextCursor.MoveOperation.End)
# 或:
cur = self.textCursor()
cur.movePosition(QTextCursor.MoveOperation.End)
self.setTextCursor(cur)
self.ensureCursorVisible()
然后再跑:
.\.venv\Scripts\python.exe .\main.py

.venv。用基准 Python 直跑(验证是否仅 venv 问题)
"C:\Users\CQJY\AppData\Local\Programs\Python\Python313\python.exe" .\main.py
如果缺包,就在 venv 中 pip freeze > requirements.txt,然后对基准环境 pip install -r requirements.txt --user(临时验证用)。
.venv\Scripts\python.exe 被破坏 导致的'不是有效应用程序'。.venv → 重建 → 重新装依赖 就能恢复;随后把上次的 QTextCursor 写法改掉,程序应能正常运行。Set-Content;不要对 .exe / .dll 之类二进制用这些命令。打包成功后双击 exe 文件会有这个弹窗报错。

报错窗口里写得很直白:No module named 'PyQt6'。
意思是打包产物里没把 PyQt6 包进去(或运行时没能找到它),所以 exe 一启动、执行到 import PyQt6 就挂了。
最常见原因与修法👇(按顺序做,基本一次好):
在 dist 目录里看一眼(你现在是 onedir 形态,PyInstaller 6.x 会把内容放到 _internal 子目录里):
dist\Diginv_BAT32G139\_internal\lib\PyQt6\__init__.py 等一堆文件/Qt 插件目录。PyQt6 这个目录,就印证了:打包时没收进去。你的路径里有空格与括号,后续命令都要加引号并用调用运算符
&。
cd "C:\Users\CQJY\Desktop\PythonProject_20251106_14_30 V1.1.3(阶段性成功)"
# 1) 这个 python 应该是你项目的 .venv
& ".\.venv\Scripts\python.exe" -c "import sys; print(sys.executable)"
# 2) 这里必须能 import PyQt6(否则先安装)
& ".\.venv\Scripts\python.exe" -c "import PyQt6, sys; print('OK:', PyQt6.__file__)"
# 若报错,再装:
& ".\.venv\Scripts\python.exe" -m pip install -U PyQt6
如果上一步 OK,说明你的构建环境有 PyQt6,继续第 2 步。
先删老的构建输出,再用'全收集'参数:
Remove-Item -Recurse -Force .\build,.\dist,.\Diginv*.spec -ErrorAction SilentlyContinue
& ".\.venv\Scripts\python.exe" -m pip install -U "pyinstaller>=6.7"
# onedir(便于检查)
& ".\.venv\Scripts\pyinstaller.exe" `
--noconfirm --clean --windowed `
--name Diginv_BAT32G139 `
--collect-all PyQt6 `
--collect-submodules PyQt6 `
--collect-data PyQt6 `
--collect-binaries PyQt6 `
--hidden-import PyQt6 `
--hidden-import PyQt6.QtWidgets `
--hidden-import PyQt6.QtCore `
--hidden-import PyQt6.QtGui `
--hidden-import PyQt6.QtSerialPort `
--add-data".\ui\assets;ui\assets" `
--icon ".\ui\assets\app.ico" `
.\main.py
PS C:\Users\xsshu\Desktop\Python\random_pro> & ".\.venv\Scripts\pyinstaller.exe" `
>> --noconfirm --clean --windowed `
>> --name RandomGame_V1.0.2 `
>> --collect-all PyQt6 `
>> --collect-submodules PyQt6 `
>> --collect-data PyQt6 `
>> --collect-binaries PyQt6 `
>> --hidden-import PyQt6 `
>> --hidden-import PyQt6.QtWidgets `
>> --hidden-import PyQt6.QtCore `
>> --hidden-import PyQt6.QtGui `
>> --hidden-import PyQt6.QtSerialPort `
>> --add-data".\ui\asset;ui\asset" `
>> --icon ".\ui\asset\main_windows_ico_32.png" `
>> .\main.py
解释
--collect-all PyQt6通常已足够,但个别环境里加上--collect-submodules/--collect-data/--collect-binaries和几个--hidden-import更保险。这些参数不改你源码,只是指导 PyInstaller 把 PyQt6 的包体与 Qt 插件全部打进来。构建完成后,dist\Diginv_BAT32G139\_internal\lib\PyQt6\...就应该有了。
(这里基本上就打包成功了。但会遇到个图标显示的问题,参考后面的解释。)
确认 onedir 能运行后,再出 onefile:
& ".\.venv\Scripts\pyinstaller.exe" `
--noconfirm --clean --windowed --onefile `
--name Diginv_BAT32G139 `
--collect-all PyQt6 `
--collect-submodules PyQt6 `
--collect-data PyQt6 `
--collect-binaries PyQt6 `
--hidden-import PyQt6 `
--hidden-import PyQt6.QtWidgets `
--hidden-import PyQt6.QtCore `
--hidden-import PyQt6.QtGui `
--hidden-import PyQt6.QtSerialPort `
--add-data".\ui\assets;ui\assets" `
--icon ".\ui\assets\app.ico" `
.\main.py
在项目根新建 Diginv.spec(这不是改源码),内容:
# Diginv.spec
from PyInstaller.utils.hooks import collect_submodules, collect_data_files, collect_dynamic_libs
hiddenimports = collect_submodules('PyQt6')
datas = collect_data_files('PyQt6')
binaries = collect_dynamic_libs('PyQt6')
# 带上你的资源
datas += [('ui/assets','ui/assets')]
block_cipher = None
a = Analysis(['main.py'], pathex=[], binaries=binaries, datas=datas, hiddenimports=hiddenimports, hookspath=[], runtime_hooks=[], excludes=[], noarchive=False)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz, a.scripts, a.binaries, a.zipfiles, a.datas,
name='Diginv_BAT32G139', console=False,# windowed
icon='ui/assets/app.ico')
coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, name='Diginv_BAT32G139')
打包:
Remove-Item -Recurse -Force .\build,.\dist -ErrorAction SilentlyContinue
& ".\.venv\Scripts\pyinstaller.exe" .\Diginv.spec
& ".\.venv\Scripts\pyinstaller.exe",别用全局 pyinstaller。_internal。exe + _internal 文件夹、但运行提示找不到 PyQt6 → 基本就是 _internal\lib 里没 PyQt6 包(或被杀软删了)。_internal\lib\PyQt6 是否存在,再双击运行。需要的话,你把 构建日志里关于 PyQt6 的几行(WARNING: Hidden import not found 之类)贴出来,我可以据此最小化参数(把多余的 --hidden-import 去掉),保证以后打包命令更干净。
在执行 2)清理后用更稳的打包命令重建(收全 PyQt6)的时候报错:

报错的意思是:你传给 --icon 的那个 app.ico不是一个符合要求的 Windows ICO(很可能是 PNG 改后缀、或只包含 PNG-压缩的条目、或文件损坏)。在 Windows 上 PyInstaller 只接受 .ico 或 .exe 的图标资源。如果安装了 Pillow,它可以把 PNG 等自动转成合规 ICO;否则就抛出你看到的错误。
给你三个'立刻可用'的修法,任选其一就能过去(都不改你项目源码)。
cd "C:\Users\CQJY\Desktop\PythonProject_20251106_14_30 V1.1.3(阶段性成功)"
& ".\.venv\Scripts\python.exe" -m pip install -U pillow
# 重新打包时把 --icon 指向 PNG(PyInstaller 会借助 Pillow 自动转成 .ico)
确保
.\\ui\\assets\\app.png存在,最好是正方形 256×256 带透明背景。

如图所示,为什么不打包单独运行 main.py 的时候主页左上角的图标正常显示,但是打包 exe 文件后,可执行文件的 logo 是有了,但是进入主页的左上角图标没有了。

原因很简单:
Explorer 里看到的 exe 图标 是 --icon 给 exe 设置的'文件图标';
窗口左上角的图标 是你的代码里 setWindowIcon(QIcon("...")) 去加载的运行时图片文件。
打包后,这个图片没有出现在 exe 运行目录的预期位置(或被放进 _internal/临时目录里,程序用相对路径找不到),于是窗口就显示成默认的小白框。
你截图里的 onedir 产物里,exe 旁边只有 _internal,没有 ui\assets\...,说明 --add-data 并没把资源放到你代码期望的相对路径。
清理后重打,确保把 ui\assets 整个目录复制到 exe 旁,并且目标子目录名保持一致:
cd "C:\Users\CQJY\Desktop\PythonProject_20251106_14_30 V1.1.3(阶段性成功)"
Remove-Item -Recurse -Force .\build,.\dist,.\Diginv*.spec -ErrorAction SilentlyContinue
# 用项目 venv 的 PyInstaller
& ".\.venv\Scripts\python.exe" -m pip install -U pyinstaller
# onedir(便于检查)
& ".\.venv\Scripts\pyinstaller.exe" `
--noconfirm --clean --windowed `
--name Diginv_BAT32G139 `
--collect-all PyQt6 `
--add-data"ui\assets;ui\assets" ` # ← 关键:把整个 ui\assets 带到 dist\...\ui\assets
--icon "ui\assets\app.ico" `
.\main.py
打完后检查:
dist\Diginv_BAT32G139\
Diginv_BAT32G139.exe
ui\assets\app.ico ← 必须存在
ui\assets\...其他图片...
_internal\...
现在双击运行,窗口左上角图标应恢复正常。
注意:在 PowerShell 里,
--add-data的分隔符是;(我们已用对了);不要写成".\ui\assets;ui\assets"也没问题,但更稳的是去掉.直接用相对路径"ui\assets;ui\assets";若你只想带单个文件,也可以多写几条:
--add-data "ui\assets\app.png;ui\assets" --add-data "ui\assets\app.ico;ui\assets"
不想重打也行:把你项目里的 ui\assets 整个文件夹手动复制到
dist\Diginv_BAT32G139\ui\assets\
保证文件名/相对路径与源码一致(比如源码里写的是 ui/assets/app.png 就要有这条路径)。再次运行,窗口图标立即正常。
python main.py 正常、打包后不正常?ui/assets/... 能被找到;_internal;如果你没把资源复制到 exe 旁的相同相对路径,QIcon("ui/assets/...") 找不到文件,Qt 不会抛异常,只是静默失败显示默认图标。onefile 会把资源解压到临时目录,与你的相对路径不一致;在不改源码的前提下,建议:
ui\assets 文件夹(但这就不是纯单文件了)。真要 onefile 且纯单文件,就需要在代码里用
sys._MEIPASS定位解包目录——这就属于'改源码',与你当前约束冲突。
--add-data "ui\assets;ui\assets" 让打包产物中出现 dist\...\ui\assets\...;或手工把该目录拷到 exe 旁。--icon 只设置前者;后者取决于运行时加载到的图片文件。


微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online