HaujetZhao / PyInstaller-Perfect-Build-Method

如果我要写一个 Python 项目,打包成 exe 运行(方便在没有 Python 的电脑上使用),我需要打包出的根目录结构美观,没有多余的、杂乱的依赖文件在那里碍眼,而且需要在发现 bug 时,我还需要能够修改里面的代码后,无需再次打包,就能正常运行,该怎么做呢? 就以一个 Hello 项目为例,记一下我找到的完美方法。
97 stars 24 forks source link

包含 numpy 依赖时,需要将 numpy 额外完整复制 #4

Open wangyexiang opened 1 year ago

wangyexiang commented 1 year ago

我在hello_main.py文件中导入了pandas,然后进行打包,报错如下:

Traceback (most recent call last):
  File "hello.py", line 3, in <module>
  File "E:\hello\hello_main.py", line 4, in <module>
    import pandas as pd
  File "E:\hello\libs\pandas\__init__.py", line 16, in <module>
    raise ImportError(
ImportError: Unable to import required dependencies:
numpy:

IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!

Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.

We have compiled some common reasons and troubleshooting tips at:

    https://numpy.org/devdocs/user/troubleshooting-importerror.html

Please note and check the following:

  * The Python version is: Python3.10 from "E:\hello\hello.exe"
  * The NumPy version is: "1.25.2"

and make sure that they are the versions you expect.
Please carefully study the documentation linked above for further help.

Original error was: DLL load failed while importing _multiarray_umath: 找不到指定的模块。

[8640] Failed to execute script 'hello' due to unhandled exception!

根据错误提示,好像是numpy中的C扩展接口的路径找不到(导入出错),请问,这种问题应该如何解决呢?

HaujetZhao commented 1 year ago

Pyinstaller 打包的 numpy 不完整。

在 spec 模板中找到:

# 这是要额外复制的模块
manual_modules = []
for m in manual_modules:
    if not find_spec(m): continue
    src = dirname(find_spec(m).origin)
    dst = m
    datas.append((src, dst))

加入 numpy :

# 这是要额外复制的模块
manual_modules = ['numpy', ]
for m in manual_modules:
    if not find_spec(m): continue
    src = dirname(find_spec(m).origin)
    dst = m
    datas.append((src, dst))
wangyexiang commented 1 year ago

感谢作者的回答,昨晚看你的博客回答里,找到了这个解决方法,目前numpy没问题了。但对paddle(百度飞桨)打包又出问题了,看错误提示,还是路径的问题,所以,我如法炮制把paddle也加入到manual_modules中,运行打包好的程序时还是报错:

Traceback (most recent call last):
  File "main.py", line 16, in <module>
  File "E:\ANNTool\libs\modules\ui\__init__.py", line 9, in <module>
    from .Mainwindow import Mainwindow
  File "E:\ANNTool\libs\modules\ui\Mainwindow.py", line 17, in <module Mainwindow>
  File "E:\ANNTool\libs\modules\ui\components\setmodelapplicationwidget.py", line 10, in <module setmodelapplicationwidget>
  File "E:\ANNTool\libs\paddle\__init__.py", line 31, in <module>
    from .framework import monkey_patch_variable
  File "E:\ANNTool\libs\paddle\framework\__init__.py", line 17, in <module>
    from . import random  # noqa: F401
  File "E:\ANNTool\libs\paddle\framework\random.py", line 17, in <module>
    from paddle import fluid
  File "E:\ANNTool\libs\paddle\fluid\__init__.py", line 36, in <module>
    from . import framework
  File "E:\ANNTool\libs\paddle\fluid\framework.py", line 35, in <module>
    from . import core
  File "E:\ANNTool\libs\paddle\fluid\core.py", line 394, in <module>
    set_paddle_lib_path()
  File "E:\ANNTool\libs\paddle\fluid\core.py", line 386, in set_paddle_lib_path
    lib_dir = os.path.sep.join([site.USER_SITE, 'paddle', 'libs'])
TypeError: sequence item 0: expected str instance, NoneType found

请问一下,这是什么原因呢?

HaujetZhao commented 1 year ago

hook.py 改成这个试下:

import sys
from pathlib import Path

BASE_DIR = Path(__file__).parent
sys._MEIPASS= str(BASE_DIR / 'libs')

# 为所有已存在于 sys.path 中的路径都插入 libs ,加到 sys.path 中
# 让程序到 libs 文件夹查找依赖
for p in sys.path.copy():
    relative_p = Path(p).relative_to(BASE_DIR)
    new_p = BASE_DIR / 'libs' / relative_p
    sys.path.insert(0, str(new_p))

sys.path.insert(0, str(BASE_DIR))    # 把运行文件所在的根目录排到第一位,优先从根目录查找依赖包

import site
site.USER_SITE = 'libs' # pyinstaller禁用了 USER_SITE 变量,但飞浆要用,那就手动添加上
HaujetZhao commented 1 year ago

目前的 pyinstaller 最新版本是 5.13,在下一个大版本(即 6.0 ),将加入这个功能,把所有非执行文件放到一个子文件夹中,到时候应该就不用这个项目了。

wangyexiang commented 1 year ago

好的,明白了。感谢解答