背景:
最近用python开发一个程序,程序需求在没有安装python的电脑上运行。对比python的打包exe工具之后我选择py2exe(py2exe官方已经不更新,由第三方人员开发维护)。
在使用过程中发现py2exe打包后出现一些文件库丢失,因此特意记录下。
简述:
py2exe是一个Python Distutils扩展,它将Python脚本转换为可执行的Windows程序,无需安装Python即可运行。
这里主要是介绍使用py2exe打包出错的排查思路,py2exe的用法请参考py2exe官网: http://www.py2exe.org/
测试环境:
系统:win10 x64
python3.6.3
依懒:
oss2==2.8.0
py2exe==0.9.3.2 # 下载地址:https://github.com/albertosottile/py2exe/releases
# pycryptodome==3.8.2 安装oss2是会自动下载安装, 安装后的目录名称是Crypto
测试代码app.py
import oss2
class OSSHandler:
def __init__(self):
self.endpoint = "endpoint"
self.auth = oss2.Auth("access_key_id", "access_key_secret")
self.bucket = oss2.Bucket(self.auth, self.endpoint, "bucket_name")
def iterator(self):
"""
遍历bucket文件
:return:
"""
for object_info in oss2.ObjectIterator(bucket=self.bucket):
print(object_info.key)
def main():
OSSHandler().iterator()
if __name__ == "__main__":
main()
打包代码setup.py, 注:console要使用终端运行,否则会一闪而过
import py2exe
from distutils.core import setup
py2exe_options = {
"dist_dir": "dist",
"compressed": 1,
"optimize": 2,
"ascii": 0,
}
setup(
name='oss',
version='0.1.0',
description="win tool",
options={'py2exe': py2exe_options},
# 注:console要使用终端运行,否则会一闪而过
console=[{
# windows = [{
"script": "app.py",
}],
zipfile="lib/shared.lib",
data_files=[],
)
打包命令
python setup.py py2exe
报错
运行dist文件夹app.exe得到错误, 发现找不到模块
Traceback (most recent call last):
File "app.py", line 1, in <module>
import oss2
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
File "oss2\__init__.pyc", line 3, in <module>
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
File "oss2\models.pyc", line 10, in <module>
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
File "oss2\utils.pyc", line 30, in <module>
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
File "Crypto\Cipher\__init__.pyc", line 27, in <module>
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 656, in _load_unlocked
File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
File "Crypto\Cipher\_mode_ecb.pyc", line 47, in <module>
File "Crypto\Util\_raw_api.pyc", line 300, in load_pycryptodome_raw_lib
OSError: Cannot load native module 'Crypto.Cipher._raw_ecb': Trying '_raw_ecb.cp36-win_amd64.pyd': [WinError 126] 找不到指定的模块。, Trying '_raw_ecb.pyd': [WinError 126] 找不到指定的模块。
查看distlibshared.lib文件,用解压工具可以解压查看, 发现py2exe工具没有把*.pyd文件打包进去,导致模块查找失败
修改
根据上面的报错信息可以看到模块调用文件位置是"CryptoUtil_raw_api.pyc", line 300
try:
filename = basename + ext
# 通过调试发现pycryptodome_filename函数查到不到*.pyd导致出错
return load_lib(pycryptodome_filename(dir_comps, filename),
cdecl)
except OSError as exp:
attempts.append("Trying '%s': %s" % (filename, str(exp)))
查看pycryptodome_filename函数CryptoUtil_file_system.py
util_lib, _ = os.path.split(os.path.abspath(__file__))
root_lib = os.path.join(util_lib, "..")
# 添加打印信息,查看模块加载位置
print("root_lib: ", root_lib)
return os.path.join(root_lib, *dir_comps)
添加打印信息,查看模块加载位置, 发现目录是shared.lib下面,但.pyd没有打包进去, 所以只要让程序加载到.pyd就可以了。
root_lib: D:\code\dist\lib\shared.lib\Crypto\Util\..
修改*.pyd加载目录, 使root_lib加载目录distlib, 注意:此方法会影响未打包的oss和pycryptodome正常使用
# util_lib, _ = os.path.split(os.path.abspath(__file__))
# root_lib = os.path.join(util_lib, "..")
util_lib = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
root_lib = os.path.join(util_lib, "Crypto")
return os.path.join(root_lib, *dir_comps)
重新打包, 复制Crypto文件夹到distlib下,Libsite-packagesCrypto文件夹只需要保留*.pyd文件,其他的可以删除
发现没有报模块加载不到,而是报*.json文件加载不到
参考上面的方法, 修改Libsite-packagesaliyunsdkcoreutils_init_.py文件
# base_dir = os.path.dirname(os.path.abspath(aliyunsdkcore.__file__))
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(aliyunsdkcore.__file__))))
复制Libsite-packagesaliyunsdkcoredata到distlib下,最终目录
运行
至此程序就可能正常运行了
通过上面的思路,打包py2exe遇到文件加载不到时,可以通过console模式查找文件加载的目录,然后进行相应的修改。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。