_memimporter在py2exe中还存在吗?
我最近才开始使用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 个回答
没错,这个过程真是麻烦……不过,关于问题的第一部分,_memimporter
似乎还是存在的,因为最近有相关讨论:
- 在3.10中移除 _PyImport_FindExtensionObject() 限制了自定义扩展模块加载器 · Issue #89470 · python/cpython - 这导致了一个补丁:
- _memimporter: 切换到 sys.modules 字典以检查重新导入 · py2exe/py2exe@2032a6e
关于如何获取 _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
相关的错误。