项目场景 & 问题描述:

由于更换电脑,本人将 Pyside6 工程项目迁移到新电脑(均为 Windows11 系统)。本人使用 Anaconda 的一个虚拟环境(名称为 py3.11),在项目根目录下使用 venv 包创建了专用于 Pyside6 项目开发的虚拟环境(目录为 .\venv),利用旧电脑上生成的 requirements.txt 使用 pip 重新安装新环境。

requirements.txt 内容如下:

altgraph==0.17.4
astroid==3.0.3
autopep8==2.0.4
colorama==0.4.6
contourpy==1.2.1
cycler==0.12.1
dill==0.3.8
docstring-to-markdown==0.15
flake8==7.0.0
fonttools==4.51.0
imageio==2.34.0
importlib-metadata==7.0.1
isort==5.13.2
jedi==0.19.1
kiwisolver==1.4.5
lazy_loader==0.3
llvmlite==0.42.0
matplotlib==3.8.4
mccabe==0.7.0
networkx==3.2.1
numba==0.59.1
numpy==1.26.4
opencv-python==4.9.0.80
packaging==23.2
parso==0.8.3
pefile==2023.2.7
pillow==10.2.0
platformdirs==4.2.0
pluggy==1.4.0
pycodestyle==2.11.1
pydocstyle==6.3.0
pyflakes==3.2.0
pylint==3.0.4
pyparsing==3.1.2
PySide6==6.6.2
PySide6_Addons==6.6.2
PySide6_Essentials==6.6.2
python-dateutil==2.9.0.post0
python-lsp-jsonrpc==1.1.2
python-lsp-server==1.10.0
pytoolconfig==1.3.1
pywin32-ctypes==0.2.2
PyYAML==6.0.1
rope==1.12.0
scikit-image==0.22.0
scipy==1.12.0
shiboken6==6.6.2
six==1.16.0
snowballstemmer==2.2.0
tifffile==2024.2.12
tomli==2.0.1
tomlkit==0.12.4
ujson==5.9.0
whatthepatch==1.0.5
yapf==0.40.2
zipp==3.17.0

环境装好后,项目直接使用 venv\Scripts\python.exe -u main.py 运行没有任何问题,但在使用 Pyinstaller 打包后,运行 dist 下的 exe 可执行文件,程序直接闪退。

Pyinstallerspec 文件内容为:

# -*- mode: python ; coding: utf-8 -*-
# from PyInstaller.utils.hooks import collect_submodules


a = Analysis(
    ['mainwindow.py'],
    pathex=[],
    binaries=[],
    datas=[('icons', 'icons'),('fonts','fonts'),('config.yaml', '.'),('log_update','.')],
    hiddenimports=['skimage'],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='xxx',
    contents_directory='.',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon='icons/xxx.ico',
)
coll = COLLECT(
    exe,
    a.binaries,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='xxx',
)

exe 的报错信息(使用命令行运行 exe 可观测到报错信息)如下:

Traceback (most recent call last):
  File "PyInstaller\hooks\rthooks\pyi_rth_pkgres.py", line 170, in <module>
  File "PyInstaller\hooks\rthooks\pyi_rth_pkgres.py", line 37, in _pyi_rthook
  File "PyInstaller\loader\pyimod02_importers.py", line 384, in exec_module
  File "pkg_resources\__init__.py", line 42, in <module>
  File "PyInstaller\loader\pyimod02_importers.py", line 384, in exec_module
  File "plistlib.py", line 70, in <module>
  File "PyInstaller\loader\pyimod02_importers.py", line 384, in exec_module
  File "xml\parsers\expat.py", line 4, in <module>
ImportError: DLL load failed while importing pyexpat: 找不到指定的模块。
[PYI-74984:ERROR] Failed to execute script 'pyi_rth_pkgres' due to unhandled exception!

核心报错信息为: ImportError: DLL load failed while importing pyexpat: 找不到指定的模块。

旧电脑上没有遇到该问题。


原因分析:

重装环境、查遍互联网,网友提出了很多解决方案,包括但不限于:

  1. 更新所有包,包括改变 Pyinstallersetuptools 版本(没用)

  2. 指定 Pyinstallerpath 参数为缺失的 DLL 路径--path 参数、spec 文件中的 pathex 路径)(没用)

  3. 指定 datasbinaries 为 DLL 路径(不完全有用)

    将缺失的 DLL 文件全部加入 binaries 也可以,但不够优雅。请见后文分析。

  4. hiddenimports 引入 exe 运行时报错的包(没用)

  5. 其它奇奇怪怪的方法(安装 Microsoft Visual C++ 2015 ???

看来只能自行分析了(汗)。本人进入虚拟环境后,执行 import pyexpat 没有报错,说明库是正常安装的(而且 python 运行项目也没有报错),问题可能出在 Pyinstaller

检查 Pyinstaller 打包时的输出信息,发现在加载部分包时,丢失 DLL 文件:

116385 WARNING: Library not found: could not resolve 'ffi-8.dll', dependency of 'D:\\Software\\anaconda3\\envs\\py3.11\\DLLs\\_ctypes.pyd'.
116386 WARNING: Library not found: could not resolve 'libcrypto-3-x64.dll', dependency of 'D:\\Software\\anaconda3\\envs\\py3.11\\DLLs\\_hashlib.pyd'.
116386 WARNING: Library not found: could not resolve 'liblzma.dll', dependency of 'D:\\Software\\anaconda3\\envs\\py3.11\\DLLs\\_lzma.pyd'.
116386 WARNING: Library not found: could not resolve 'libbz2.dll', dependency of 'D:\\Software\\anaconda3\\envs\\py3.11\\DLLs\\_bz2.pyd'.
116387 WARNING: Library not found: could not resolve 'libcrypto-3-x64.dll', dependency of 'D:\\Software\\anaconda3\\envs\\py3.11\\DLLs\\_ssl.pyd'.
116387 WARNING: Library not found: could not resolve 'libssl-3-x64.dll', dependency of 'D:\\Software\\anaconda3\\envs\\py3.11\\DLLs\\_ssl.pyd'.
116388 WARNING: Library not found: could not resolve 'libexpat.dll', dependency of 'D:\\Software\\anaconda3\\envs\\py3.11\\DLLs\\pyexpat.pyd'.
116388 WARNING: Library not found: could not resolve 'tcl86t.dll', dependency of 'D:\\Software\\anaconda3\\envs\\py3.11\\DLLs\\_tkinter.pyd'.
116388 WARNING: Library not found: could not resolve 'tk86t.dll', dependency of 'D:\\Software\\anaconda3\\envs\\py3.11\\DLLs\\_tkinter.pyd'.
116389 WARNING: Library not found: could not resolve 'tbb12.dll', dependency of '项目路径\\venv\\Lib\\site-packages\\numba\\np\\ufunc\\tbbpool.cp311-win_amd64.pyd'.

推测是 libexpat.dll 等关键 DLL 文件在 Anaconda 路径 D:\\Software\\anaconda3\\envs\\py3.11\\DLLs 无法找到 。

本人在项目的虚拟环境目录 .\venv 下搜索缺失的 DLL 文件,确实无法找到(不明原因),但在 Anaconda 环境中找到了这些 DLL 文件,路径为 D:\\Software\\anaconda3\\envs\\py3.11\\Library\\bin\\,说明 Pyinstaller 找错地方了(不明原因)。

Pyinstaller 的输出信息中,观察 INFO: Extra DLL search directories (AddDllDirectory)INFO: Extra DLL search directories (PATH) 路径,发现搜索路径不包含 D:\\Software\\anaconda3\\envs\\py3.11\\Library\\bin\\,说明可能是路径错误导致找不到 DLL 文件。

  • 一种解决方法是将缺失的 DLL 文件全部加入 Pyinstaller 的 binaries 中,但不太优雅;
  • 另一种方法是希望 Pyinstaller 自动查找缺失的 DLL 文件。查遍互联网,暂未找到如何将 DLL 路径加入 AddDllDirectory 的方法,但是我们可以加入 PATH,曲线救国。

解决方案:

方法一(不太优雅):

spec 文件开头加上:

PYTHON_DLL_PATH = "D:\\Software\\anaconda3\\envs\\py3.11\\Library\\bin\\"
addition_dlls = ['libexpat.dll', 'liblzma.dll', 'libbz2.dll', 'libcrypto-3-x64.dll', 'libssl-3-x64.dll', 'ffi-8.dll', 'tk86t.dll', 'tcl86t.dll', 'tbb12.dll']
addition_binaries = [(f"{PYTHON_DLL_PATH}", dll) for dll in addition_dlls]

其中 addition_dlls 包含 Pyinstaller 找不到的 DLL 文件名,PYTHON_DLL_PATH 更换为读者自己的 Anaconda 虚拟环境的 Library\\bin 路径。

然后 binaries 赋值为:

a = Analysis(
    ...
    binaries=addition_binaries,
    ...
)

再执行 pyinstaller xxx.spec 打包。

方法二(比较优雅):

将 DLL 路径临时加入系统 PATH 环境变量,即在 spec 文件开头加上:

import os
os.environ['PATH'] = f"{os.environ['PATH']};D:\\Software\\anaconda3\\envs\\py3.11\\Library\\bin\\"

DLL 路径 更换为读者自己的 Anaconda 虚拟环境的 Library\\bin 路径。

再执行 pyinstaller xxx.spec 打包。
运行 dist 下的 exe 文件,正常运行。


思考:

按理说 Pyinstaller 不应找不到 DLL 文件,且 DLL 文件竟然都在 Anaconda 环境中、而非项目的虚拟环境 .\venv 中,本人尚未知晓原因。以上方法仅为权宜之计,治标不治本,若有更好方法欢迎评论区交流。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐