Java调用PyInstaller编译的Python二进制文件
Java调用PyInstaller打包的Python脚本二进制文件 这是一个悲伤的故事,事情的起因是业务需要做一些简单的数据分析,然后业务组有一位数学分析的海归硕士,就提出,分析核心功能他们自己实现,我们这边直接调用就行。实事真是如此吗?
在定方案的时候,我说就,既然你们做数据分析,那么,你们就基于Python实现一套完整逻辑的接口,基于HTTP或者GRPC给我们都可以。然而,数据分析的同事仅是学数据分析的,掌握Python的程度并没有那么深,只是在数据分析相关的Python库有所造诣。为了降低难度,最后选择了我们用Java去执行Python脚本,在Windows上开发的时候,一切也比较顺利,毕竟Python环境一安装,相关依赖安装好,就可以直接使用 Runtime.getRuntime().exec() 进行调用。
但是在我们部署到Docker上的时候,就遇到了一些麻烦。我们的Docker是基于openjdk:8u252-jdk
的基础镜像,每次在打包镜像安装Python环境,安装依赖也比较麻烦。后来就想了一个办法,要不就直接将脚本编译为独立的二进制文件,这样依赖,直接复制过去就可以直接执行了。
在整个过程也遇到了一些问题,这里便记录一下。
问题1
选择什么编译Python脚本呢?处于部署环境的原因,我们需要在pyinstaller和 nuitka之间做选择。
PyInstaller
- 安装简便,可以通过pip轻松安装
- 提供了丰富的文档和教程,帮助用户快速上手
- 打包速度较快,特别是在处理小型项目时
- 生成的可执行文件体积相对较大,这可能会影响应用的性能和加载时间
- 支持跨平台操作,包括Windows、Linux和macOS等操作系统
- 自动检测Python脚本的依赖项,并将它们打包到生成的可执行文件中
- 兼容性较好,能够正确打包许多主要的Python包
- 生成的可执行文件虽然看起来更加安全,但由于其仍然依赖于解释器,因此存在被逆向工程的风险
- 支持单文件发布,可以将整个Python应用程序及其所有依赖项打包成一个单独的可执行文件
Nuitka
- 安装简便,可以通过pip轻松安装
- 相对于PyInstaller,Nuitka的编译过程可能稍显复杂
- 打包速度相对较慢,尤其是在处理大型项目时
- 生成的C代码可执行文件体积较小,且运行效率更高
- 通过将Python代码编译为C代码,Nuitka显著提高了代码的执行速度,特别是在数值计算和数据处理方面。
- 可以编译大部分Python代码,并且生成的C代码可以与其他C代码库集成,但由于生成的C代码依赖于目标平台的C编译器,因此其可移植性相对较差
- 将Python代码编译为C代码后,增加了代码的保护性,降低了被逆向工程的可能性
鉴于目前的情况,我们并不需要在这方面投入太多时间,就选择了较为简单的PyInstaller
,PyInstaller
会将所有必要的文件和资源(包括Python解释器、依赖库文件和程序代码)一起打包到一个二进制文件中。这个二进制文件是独立的可执行文件,可以直接在目标环境中运行,当运行可执行文件时,PyInstaller会模拟一个完整的Python环境,包括加载必要的依赖项和模块。这使得Python程序可以在没有实际Python解释器的环境中运行。
问题2
1 | Could not fetch URL https://pypi.org/simple/numpy/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/numpy/ (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1091)'))) - skipping |
这是默认镜像地址的原因,可修改默认的镜像地址,为了方便,我们直接在下载的时候,带上镜像源
1 | pip install seaborn -i http://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com |
镜像源地址,大家可以在网上查一下,我这里用的是阿里的镜像源http://mirrors.aliyun.com/pypi/simple
问题3
1 | findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans. |
我们在程序里面设置了中文字体
1 | # 设置中文显示字体 |
但是在Linux中没有这个字体,所以,就需要下载对应的字段。
我们可以在其他地方去找到SimHei.ttf
然后复制到linux的/usr/share/fonts/
中。
问题4-PyInstaller编译后,依赖不存在
程序里面有引用,但是一直报找不到依赖。通过--hidden-import
参数,执行漏掉的依赖。
PyInstaller命令简单介绍
PyInstaller命令的基本语法如下:
1 | pyinstaller [options] script[.py] |
其中,script是要打包的Python脚本文件的文件名,可以带.py后缀;options是可选的命令行选项,可以用来指定打包的方式、输出文件的位置等。
1 | -F或--onefile 将所有文件打包为一个单独的可执行文件。 |
最终,使用pyinstaller -d noarchive -F --hidden-import seaborn DY.py
将我们的脚本编译为二进制,编译后的文件是真的大的,接近50MB
。
最后,我们直接在代码中通过Runtime.getRuntime().exec()
来调用编译好的二进制文件。
在Dockerfile中,我们加入相应的文件
1 | FROM openjdk:8u252-jdk |
这样一来字体加二进制文件,整个镜像又多了60MB+,虽然目的咱们达到了,但是这种方式,我…
Java调用PyInstaller编译的Python二进制文件