(欢迎分享源网页,禁止未经本人书面许可转载至他处,或进行其他不当使用行为)
Python 脚本在装有 Python 的系统中可以直接双击运行,但绝大多数普通用户并没有配置此类环境,而编译为可执行二进制文件后,用户无需预先安装 Python 及依赖库即可像运行普通程序一样运行您的代码。
有相当数量的 Python 库可以实现此类转换,著名的有 py2exe、py2app、PyInstaller、cx_Freeze 等。其中,py2app 适用于 macOS,不在本文讨论范围内;py2exe、PyInstaller 因为 Python 3.6 通过 PEP 523 等修改了代码对象(code object)的实现而暂未能继续支持。因此,cx_Freeze 属于目前可用的最佳方案之一。
根据其官方文档,cx_Freeze 需要通过一个简单的安装脚本来进行构建。一般来说,只要 Python 脚本本身能无错运行在自身环境,且 cx_Freeze distutils 安装脚本配置得当,那么构建出的可执行文件就能在任何相同操作系统下运行。作为开始,首先充分调试自己的 Python 脚本(不妨假设其名为 main.py)。main.py 允许从任何库或其他脚本中导入,甚至用 os.system() 或 subprocess.run() 等调用其他程序。确认无误后,在 main.py 所在目录下新建一个较简单的 cx_Freeze 脚本,不妨假设其名为 setup.py,内容如下:
import sys
from cx_Freeze import setup, Executable
# Dependencies are automatically detected, but it might need fine tuning.
build_exe_options = {'packages': [], 'excludes': []}
setup( name = '<程序名>',
version = '<程序版本>',
description = '<程序描述>',
options = {'build_exe': build_exe_options},
executables = [Executable('main.py')])
可见其基本遵照了官方文档的示例,并去除了一些暂时用不到的语句。其中,build_exe_options 暂时留空,待第一次构建后根据可能的报错信息补充;原例中的 base 实际上默认就是 None,即命令行程序,除非需要构建图形界面程序,否则配置了反而是画蛇添足。现在可以打开命令行终端,在此目录下运行
python setup.py build
所构建的可执行文件为 .\build\exe.win-<架构名>-<Python 版本号>\main.exe。不幸的是,一般而言,除非 main.py 本身过于简单,很少有能一次编译通过且 exe 正确运行的情况,只要有耐心将错误各个击破,最终还是有希望成功的。以下总结一些常见报错信息,注意很多时候这些错误并不是出现在编译时(即使 cx_Freeze 输出大量 ? 开头的缺失库也未必是问题所在),而是在运行 exe 时才会弹窗。部分问题在使用 py2exe、PyInstaller 等其他工具时也会出现(因为基本都依赖于 Python 代码对象),因而下列条目亦有一定参考价值。
1. 看似编译成功,一旦运行 exe 则报找不到某些库或导入错误,例如
ImportError: No module named '<任意库名>'
这是由于 cx_Freeze 在编译时,实际上是将全部导入库都编译了一遍并放在子目录中供主程序调用,从而真正独立于 Python 环境。其自称能够自动分析哪些库需要包含。然而事实上对深入一定层次后的导入分析有瑕疵(例如 main.py 引用了一个库,该库又引用了另一个库,则第三个库有可能分析不出)。遇到这种错误,绝大多数情况下可以手动在 setup.py 中指定额外包含的库,既可以在 build_exe_options 下的 ‘packages’ 中指定库名,也可以在 build_exe_options 中新建一个键名为 ‘includes’ 然后指定库名,形如:
build_exe_options = {'packages': ['tkinter', 'scipy'], 'includes': ['numpy.core._methods']}
这个过程有时要重复多次,直到添加完所有<del>届不到</del>不能自动检测到的库。据说也可以直接在 setup.py 中导入(import)库,未亲测。个别特例无效,请继续阅读。
2. 对 scipy.spatial.ckdtree,即使手动添加包含后,运行时依然报 ImportError,这是由于 cx_Freeze 编译时对该库文件的命名大小写与 Python 代码中不一致,而 Python 大小写敏感。解决方法是在 .\build\exe.win-<架构名>-<Python 版本号>\scipy\spatial\ 下找到 cKDTree.cp<Python 版本号>-win_<架构名>.pyd,重命名为 ckdtree.cp<Python 版本号>-win_<架构名>.pyd,不用重新编译(不然下次文件名又错了),再次运行即不会报错。当然可以在 setup.py 中添加语句,在每次编译完毕后自动重命名该文件,步骤不再赘述。
3. 编译时直接报错 KeyError: ‘TCL_Library’。此问题在 Python 3.5 或以上版本中出现,解决方法是在 setup.py 中 setup() 之前添加
import os
os.environ['TCL_LIBRARY'] = '$PYTHONDIR$\\tcl\\tcl8.6'
os.environ['TK_LIBRARY'] = '$PYTHONDIR$\\tcl\\tk8.6'
4. 运行 exe 时报错:
Traceback (most recent call last):
File "$PYTHONDIR$\lib\site-packages\cx_freeze\initscripts\Console.py"
line 21, in <module>
exec(code, m.__dict__)
File "Widgets_tmp.py", line 1, in <module>
File "$PYTHONDIR$\lib\tkinter\__init__.py", line 35, in <module>
import _tkinter#If this fails you Python may not be configured for Tk
ImportError: DLL load failed:
顾名思义,导入 Tk 相关 DLL 出错,解决方法是手动导入。在 build_exe_options 中添加键
'include_files': ['$PYTHONDIR$\\DLLs\\tcl86t.dll', '$PYTHONDIR$\\DLLs\\tk86t.dll']
即可解决。
5. 注意如果调用了其他 exe,请设法确保用户也能使用和您的脚本中相同的方式在命令行中直接调用之,例如直接将该 exe 放在 main.exe 所在目录下(当然,如果原脚本中用到了绝对路径,只能说您根本没打算让它移植到别的机子上),或设置好 PATH 环境变量。
6. 最后,确认 main.exe 正常运行,然后将整个 exe.win-<架构名>-<Python 版本号> 目录打包。没有其余文件单独运行 main.exe 显然是不行的,因此记得提醒用户。
朋友们如果在使用过程中遇到了上文没有提及的其他错误,欢迎在评论中一起切磋,共同提高。
参考文献:
[1] cx_Freeze and seaborn – ImportError: No module named 'scipy.spatial.ckdtree'