_memimporter在py2exe中还存在吗?

1 投票
1 回答
27 浏览
提问于 2025-04-13 20:06

我最近才开始使用py2exe,想把它用在MINGW64的Python3程序和相关库上。不过,我尝试的第一个例子构建失败了。

之后,我找到了这个https://www.py2exe.org/index.cgi/TroubleshootingImportErrors页面,上面提到:

检查你的系统是否能正常使用zipextimporter

zipextimporter是py2exe的一个重要组件,可能会导致问题。要调试它,你需要一个叫做_memimporter.pyd的二进制模块。这些模块可以在与你的Python版本相对应的py2exe二进制发行版中找到(我用7Zip解压.exe发行版)。

页面上还有一个测试脚本,但它是用Python 2写的(最后更新是在2011年1月7日);所以我把它转换成了Python 3的语法:

import zipextimporter
zipextimporter.install()

import sys
sys.path.insert(0, "lib.zip")

import _socket
print(_socket)
# <module '_socket' from 'lib.zip\_socket.pyd'>
print(_socket.__file__)
# 'lib.zip\\_socket.pyd'
print(_socket.__loader__)
# <ZipExtensionImporter object 'lib.zip'>
# Reloading also works correctly:
print(_socket is reload(_socket))
# True

但是当我运行它时,我得到了:

$ python3 test_zipextimporter.py
Traceback (most recent call last):
  File "D:/msys64/tmp/test_zipextimporter.py", line 1, in <module>
    import zipextimporter
  File "D:/msys64/mingw64/lib/python3.11/site-packages/zipextimporter.py", line 51, in <module>
    import _memimporter
ModuleNotFoundError: No module named '_memimporter'

所以,正如上面提到的,我需要一个_memimporter.pyd的二进制模块;在这个包里没有直接找到这样的文件:

$ pacman -Ql mingw-w64-x86_64-python-py2exe | grep _mem
$

不过,说明中还提到“这些模块可以在与你的Python版本相对应的py2exe二进制发行版中找到(我用7Zip解压.exe发行版)。”目前包里有这些二进制文件:

$ pacman -Ql mingw-w64-x86_64-python-py2exe | grep '\.exe\|\.dll'
mingw-w64-x86_64-python-py2exe /mingw64/lib/python3.11/site-packages/py2exe/resources.dll
mingw-w64-x86_64-python-py2exe /mingw64/lib/python3.11/site-packages/py2exe/run-py311-mingw_x86_64.exe
mingw-w64-x86_64-python-py2exe /mingw64/lib/python3.11/site-packages/py2exe/run_ctypes_dll-py311-mingw_x86_64.dll
mingw-w64-x86_64-python-py2exe /mingw64/lib/python3.11/site-packages/py2exe/run_w-py311-mingw_x86_64.exe

这些文件用unzip -l是无法列出的;但是用7z l可以列出——不幸的是,我没有看到任何类似_memimporter.pyd的东西——这两个.exe文件显示的文件结构是:

$ 7z l /mingw64/lib/python3.11/site-packages/py2exe/run_w-py311-mingw_x86_64.exe
...
   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2023-12-27 10:55:20 .....        24064        24064  .text
2023-12-27 10:55:20 .....         1024         1024  .data
2023-12-27 10:55:20 .....         6656         6656  .rdata
2023-12-27 10:55:20 .....         2048         2048  .pdata
2023-12-27 10:55:20 .....         2048         2048  .xdata
2023-12-27 10:55:20 .....            0            0  .bss
2023-12-27 10:55:20 .....         2048         2048  .edata
2023-12-27 10:55:20 .....         4096         4096  .idata
2023-12-27 10:55:20 .....          512          512  .CRT
2023-12-27 10:55:20 .....          512          512  .tls
                    .....          766          744  .rsrc/1033/ICON/1.ico
                    .....           20           20  .rsrc/1033/GROUP_ICON/1
                    .....         1167         1167  .rsrc/0/MANIFEST/1
2023-12-27 10:55:20 .....          512          512  .reloc
------------------- ----- ------------ ------------  ------------------------
2023-12-27 10:55:20              45473        45451  14 files

我尝试与普通的py2exe下载进行比较,结果也差不多:

$ wget https://files.pythonhosted.org/packages/b1/07/f45b201eb8c3fea1af6a9bd9f733479aa9d009139ce2396e06db7aa778c8/py2exe-0.13.0.1-cp311-cp311-win_amd64.whl
# ...
$ mkdir py2exe_0.13.0.1
$ (cd py2exe_0.13.0.1; unzip ../py2exe-0.13.0.1-cp311-cp311-win_amd64.whl)
# ...
$ 7z l py2exe_0.13.0.1/py2exe/run-py3.11-win-amd64.exe
# ...
   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2023-10-07 18:15:11 .....        20480        20480  .text
2023-10-07 18:15:11 .....        11264        11264  .rdata
2023-10-07 18:15:11 .....          512          512  .data
2023-10-07 18:15:11 .....         2048         2048  .pdata
                    .....          766          744  .rsrc/ICON/1.ico
                    .....           20           20  .rsrc/GROUP_ICON/1
                    .....          381          381  .rsrc/MANIFEST/1
2023-10-07 18:15:11 .....          512          512  .reloc
------------------- ----- ------------ ------------  ------------------------
2023-10-07 18:15:11              35983        35961  8 files

我想这个_memimporter还是存在的,因为我的测试脚本报错“没有名为'_memimporter'的模块”;而且在包的Python代码中仍然有相关的引用:

$ grep -rI _memimporter /mingw64/lib/python3.11/site-packages/py2exe
/mingw64/lib/python3.11/site-packages/py2exe/distutils_buildexe.py:##             self.excludes.append("_memimporter") # builtin in run_*.exe and run_*.dll
/mingw64/lib/python3.11/site-packages/py2exe/hooks.py:# _memimporter can be excluded because it is built into the run-stub.
/mingw64/lib/python3.11/site-packages/py2exe/hooks.py:_memimporter

... 不过,我得问一下——这个_memimporter.pyd现在还存在吗?如果存在,我该在哪里找到它呢?

1 个回答

1

没错,这个过程真是麻烦……不过,关于问题的第一部分,_memimporter 似乎还是存在的,因为最近有相关讨论:

关于如何获取 _memimporter,这又是个麻烦事,因为你需要从源代码构建。这里有一个历史版本 https://github.com/mitre/caldera-py2exe - 但由于上面提到的补丁,最好直接从主 py2exe 的 Git 仓库构建:

$ git clone https://github.com/py2exe/py2exe py2exe_git

现在,问题是用哪个命令来构建 - 不幸的是,主仓库的 README.md 并没有说明这一点(它只提到 pip install py2exe)。在历史仓库中提到一个命令 python setup.py bdist,但似乎已经不推荐使用,因为它会报错:

D:/msys64/mingw64/lib/python3.11/site-packages/setuptools/__init__.py:80: _DeprecatedInstaller: setuptools.ins
taller and fetch_build_eggs are deprecated.
!!

        ********************************************************************************
        Requirements should be satisfied by a PEP 517 installer.
        If you are using pip, you can try `pip install --use-pep517`.
        ********************************************************************************

!!
  dist.fetch_build_eggs(dist.setup_requires)

我终于找到了 MINGW64 包用来构建的命令,在相应的 MINGW-packages/mingw-w64-python-py2exe/PKGBUILD 文件中。

另外,注意我需要安装:

pacman -S mingw-w64-x86_64-python-build
pacman -S mingw-w64-x86_64-python-wheel

然后,直接使用的 py2exe 源代码是无法编译的,所以还需要应用 PKGBUILD 文件中提到的补丁。因此我们有:

$ cd py2exe_git
$ wget https://raw.githubusercontent.com/msys2/MINGW-packages/master/mingw-w64-python-py2exe/001-setup-fix.patch
# ...
$ wget https://raw.githubusercontent.com/msys2/MINGW-packages/master/mingw-w64-python-py2exe/002-mingw-fix.patch
# ...
$ patch -p1 -i 001-setup-fix.patch
patching file py2exe_setuptools.py
Hunk #5 succeeded at 194 with fuzz 1.
patching file setup.py
Hunk #1 succeeded at 23 (offset 2 lines).
Hunk #2 succeeded at 46 (offset 1 line).
Hunk #3 succeeded at 73 (offset 1 line).
Hunk #4 succeeded at 91 (offset 1 line).
Hunk #5 succeeded at 109 (offset 1 line).
Hunk #6 succeeded at 125 (offset 1 line).
$ patch -p1 -i 002-mingw-fix.patch
patching file py2exe/hooks.py
patching file py2exe/runtime.py
Hunk #1 succeeded at 307 (offset 5 lines).
patching file source/_memimporter.c
patching file source/python-dynload.c
patching file source/start.c

好的,现在我们可以构建了 - 仍然会有一个不推荐使用的警告,但构建会成功:

$ python3 -m build --wheel --skip-dependency-check --no-isolation
* Building wheel...
D:/msys64/tmp/py2exe_git/py2exe_setuptools.py:10: SetuptoolsDeprecationWarning: dep_util is Deprecated. Use fu
nctions from setuptools.modified instead.
!!

        ********************************************************************************
        Please use `setuptools.modified` instead of `setuptools.dep_util`.

        By 2024-May-21, you need to update your project and remove deprecated calls
        or your builds will no longer be supported.

        See https://github.com/pypa/setuptools/pull/4069 for details.
        ********************************************************************************

!!
  from setuptools.dep_util import newer_group
running bdist_wheel
...
adding 'py2exe-0.13.0.1.dist-info/RECORD'
removing build/bdist.mingw_x86_64/wheel
Successfully built py2exe-0.13.0.1-cp311-cp311-mingw_x86_64.whl

没错,所以这里的关键是,_memimporter.pyd 文件并没有构建成功,只有一个 .o 文件:

$ find . -name '_mem*'
./build/temp.mingw_x86_64-cpython-311/source/_memimporter.o
./source/_memimporter.c

如果我们查看构建日志,会发现这个 .o 文件直接被构建到 run-py311-mingw_x86_64.exe 中:

gcc -s ... build/temp.mingw_x86_64-cpython-311/source/_memimporter.o ... -lpython3.11 -o build/lib.mingw_x86_64-cpython-311/py2exe/run-py311-mingw_x86_64.exe -m64 -mconsole -municode

……这可能就是为什么无法从 .exe 中提取出 _memimporter.pyd 的原因。

所以,现在我们有了 .o 文件,但我们需要 .pyd - 我在 如何创建 .pyd 文件? 中找到了一个提示(幸运的是,也提到了 MSYS2/MINGW64) - 最终,这个命令是:

gcc -shared -L/mingw64/bin \
./build/temp.mingw_x86_64-cpython-311/source/_memimporter.o \
build/temp.mingw_x86_64-cpython-311/source/memorymodule.o \
build/temp.mingw_x86_64-cpython-311/source/myloadlibrary.o \
build/temp.mingw_x86_64-cpython-311/source/actctx.o \
build/temp.mingw_x86_64-cpython-311/source/python-dynload.o \
-lpython3.11 -o _memimporter.pyd

所以,现在我们得到了 _memimporter.pyd;为了让测试脚本能够工作,我们把它复制到 /mingw64/lib/python3.11/site-packages/(出于某种原因,把它复制到 /mingw64/lib/python3.11/site-packages/py2exe 不起作用):

$ cp -av /tmp/py2exe_git/_memimporter.pyd /mingw64/lib/python3.11/site-packages/
'/tmp/py2exe_git/_memimporter.pyd' -> '/mingw64/lib/python3.11/site-packages/_memimporter.pyd'

然后我们终于可以测试导入,看看没有错误:

$ python3 -c 'import _memimporter'
$

现在我们可以运行 OP 的测试脚本了 - 不过,出于某种原因,我没有看到 _socket 的打印输出:

$ python3 test_zipextimporter.py
$

……但至少没有与 _memimporter 相关的错误。

撰写回答