# 基本操作

# 多py文件打包
# pyi-makespec指定的参数被直接添加到spec中
#  -F, --onefile         Create a one-file bundled executable.
#  -w, --windowed, --noconsole # 无控制窗口
#  --add-data "\path\web;web" # 把web文件夹打包到dist\app\_internal的web中,使用该参数时建议不使用-F, --onefile指令
#  --icon=app.ico # 指定采用的图标

pyi-makespec -F -w app.py # 生成spec文件
pyinstaller app.spec # 利用spec文件打包应用

在多 python 文件开发时,可以在每个模块下面加上 __init__.py 文件,使得 python 将其识别为模块,调用时就用 from . import xx.py 或者 from .xx import xxx 之类的,然后 app.py 文件放置在和模块文件夹的同级的目录下调用该模块,这样打包时,只需把模块文件夹的目录加入 spec 文件中的 pathex 参数中便可以了(相当于一个本地库,直接加入 python 搜索路径了)。某模块文件夹的 tree 指令打印如下:

C:.
│  constants.py
│  __init__.py
│
├─core
│      ssh_service.py
│      websockets_service.py
│      __init__.py
│
├─events
│      events.py
│      __init__.py
│
├─presenters
│      main_presenter.py
│      __init__.py
│
└─views
    │  control_view.py
    │  docx_dialog_view.py
    │  file_view.py
    │  html_dialog_view.py
    │  image_dialog_view.py
    │  log_view.py
    │  main_window.py
    │  step_view.py
    │  terminal_view.py
    │  text_dialog_view.py
    │  __init__.py
    │
    └─web
            index.html
            xterm.css
            xterm.js

参考

# hook 文件的问题

诸如 failed to execute pyi_rth_pkgresFileNotFoundError: 'Lorem ipsum.txt' resource not found in 'jaraco.text' 等,都是由于钩子文件(hook file,即告诉 PyInstaller 如何正确捆绑包的脚本)的问题,使用下面指令解决该问题:

pip install https://github.com/pyinstaller/pyinstaller/archive/develop.tar.gz

参考

# 程序访问文件夹

为了使得在打包为 exe 文件后,程序仍可正确找到路径,可以用如下代码,其中 sys._MEIPASS 代表 dist\app\_internal 文件;

注意此时不宜使用 - F, --onefile 参数,不然会没有该文件

html_path = resource_path(os.path.join("web", "index.html"))
def resource_path(relative_path):
    """获取打包后资源文件的绝对路径"""
    if hasattr(sys, '_MEIPASS'):
        # 如果是打包后的环境
        base_path = sys._MEIPASS
    
    # 获取exe文件所在的目录
    # if getattr(sys, 'frozen', False):
    #     # 打包后的环境
    #     return relative_path # 直接返回相对路径
    # if getattr(sys, 'frozen', False):
    #     # 打包后的环境
    #     base_path = os.path.dirname(sys.executable)
    else:
        # 开发环境,直接使用当前路径
        # base_path = os.path.abspath(".")
        base_path = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(base_path, relative_path)

参考

# QWebEngineView 的问题

现象:在代码中 self.web_view = QWebEngineView() 创建 Web 视图,用 pyinstaller 打包为 exe 文件后,点开后会立即闪退。

原因:pyinstaller 从 pyqt5 中拷贝的库不全

解决:可以手动拷贝过去,例如用 conda 创建的虚拟环境 myUi,其 pyqt5 下的 qt5 库路径为 \path\anaconda3\envs\myUi\Lib\site-packages\PyQt5\Qt5,把 bin、plugins、qml、resources、translations 文件都拷贝到打包后 \path\dist\app_internal\PyQt5\Qt5 文件夹中进行替换就行了。

最终:不过经过排查,我所遇到的问题,只需用 \path\anaconda3\envs\myUi\Lib\site-packages\PyQt5\Qt5\bin\QtWebEngineProcess.exe 把 \path\dist\app_internal\PyQt5\Qt5\QtWebEngineProcess.exe 替换掉就能解决问题了。

参考

# 程序图标设置

在线把图片转为.ico 格式,放在打包目录下使用 --icon=app.ico 添加即可

这样仅是把桌面图标设置为了该图片,为了使得弹窗中左上角的小图标也设置为该图片,可使用 app.setWindowIcon(QIcon('app.ico')) ,简单一点的话,直接把 ico 图片放置在打包后的 exe 同目录下就行了

如果设置后桌面图标没有刷新,重启一下电脑就好了

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setWindowIcon(QIcon('app.ico'))
    win = IconSet()
    win.show()
    sys.exit(app.exec_())

参考

# 零散的

# spec 文件结构

# 体积缩减

# Nuitka

Python 打包工具 Nuitka 入门指南 - CSDN 博客

# 打包工具 Auto-Py-to-Exe