如题,找了好久,没有答案,都是打包成.exe格式的,没有linux的,请求各位大佬出谋划策!


Python本身就支持直接运行zip文件。所以本回答主要讨论基于zip文本的大一打包方法。

Python早期就允许把软体包打包到一个zip文件里,然后将zip包的文件名加入到 sys.path ,就可以导入zip包里的模块了。这种玩法还有个扩展,就是允许在zip包里放入一个文件 __main__.py ,则Python可以直接运行该zip包里的 __main__.py 。

于是将一个Python程序打包成单一文件的办法,就是将应用的所有Python文件,依赖包等,全部装入一个zip包,并在 __main__.py 里编写程序的入口。

要分析依赖包,可以使用virtualenv来搭建一个不含有第三方库的环境,并在后期用pip freeze命令来获得所有依赖的第三方库。

将第三方库打包进入zip包时,需要这些第三方库都是纯Python库。常见的不符合要求的情况是第三方库里含有.so/.dll格式的动态库,以及第三方库里包含非Python源码的资源文件。部分此类问题可以通过选择符合要求的同类模块来解决。比如连接MySQL资料库,最常用的python-mysql库就是包含.so文件的。而PyMySQL库就是纯Python的。纯Python的模块性能可能会低一些,但综合考虑对性能影响不大的也可以用。比如资料库本身的计算很耗时,而访问资料库过程的那点打包解包时间相比就不严重了。

如果还是需要使用带有.so/.dll的第三方库,会略麻烦。一个办法是程序首次启动时,就自动访问zip包本身,提取.so/.dll文件解压到本地目录。然后设置环境变数LD_LIBRARY_PATH 为包含当前目录,再启动个Python进程来运行zip包。Python的os.environ是启动时拷贝的,不会动态读取。所以Python运行期间修改自身的os.environ[LD_LIBRARY_PATH]是无效的,而必须用如上方式,修改后再启动个Python进程来运行。

用zip包的方式打包整个应用,通常使用文件扩展名pyz,但不是强制的,只是个建议。运行的方式形如 python xxx.pyz 。

如果希望脚本可以直接启动,而不是前面加个python,还可以为zip包加脚本头。就是在文件最前部加一段 #!/usr/bin/python 即可,这种方式还可以指定Python版本。随后用chmod 755 xxx.pyz加上可执行许可权就可以直接用 ./xxx.pyz 来运行了。


在linux下,一个pip就够了。

linux是开发友好的。类似的事情非常简单,所以你看不到太多这方面的文档。

在此我们假设目标linux上已经有python了,毕竟这是伺服器标配。

假如我们有以下fiblife工程

fiblife
├── app.py
├── dep
├── requirement.txt
└── src

我们可以把所有的依赖库直接安装到工程目录中的dep目录里去。

通过pip可以从pypi安装各种依赖库,包括从github

$&> pip install -t dep pymysql
$&> pip install -t dep git+https://github.com/nikoloss/pyfadeaway.git

当然,你也可以通过requirement.txt来安装

$&> pip install -t dep -r requirement.txt

之后你会发现dep就有了相应的依赖库了。

当然如果你直接执行app.py肯定是找不到dep里面的依赖的,有一个环境变数是专门干这个事情的,那就是PYTHONPATH,所以两个办法,要么改造app.py设置环境这个环境变数

import os
proj_dir = os.path.dirname(os.path.abspath(__file__))
os.environ[PYTHONPATH] = os.path.join(proj_dir, dep)

import pymyal

这种做法对开发阶段不友好,而且是无法在比如vscode中进行dep中的库提示的。所以通常我们会写一个shell脚本来启动app.py

cd `dirname`
export PYTHONPATH=$PYTHONPATH:dep
env python3 app.py

如果我们需要在vscode中开发,让ide提示dep中的库,只需要下载python和python extenssion,然后在fiblife工程目录下新建一个.env文件,内容如下

PYTHONPATH=dep

重启vscode就OK了。

需要注意的点。

python依赖库主要有3种:

  • 纯python写的库,比如pyaes,pymysql,pyyaml
  • python+so库,比如simplejson,cjson,mysqldb
  • 需要生成可执行文件,比如gunicorn

如果你的依赖库是第一种类型,也就是纯python写的,那这种方式可以很好的cover住,不管目标linux是arm还是x86,是32位还是64位,只要python版本一致将行。

如果是第二种,你就需要跟目标环境一致了,否则你安装到dep里的so库是不能运行的。

第三种情况在安装依赖的时候参照第二点,安装完成之后在dep目录下会生成一个bin目录,所以执行的时候需要加上 dep/bin/xxx比如

dep/bin/gunicorn -w 3 app:app

但是有一个不能忽视的点在于,如果dep/bin/xxx是二进位的那么这么用问题不大。但是如果是python写的,那么很有可能这么执行会报错找不到python路径,这是因为安装的时候,生成这个python文件的第一行 #!/usr/share/local/python/python3 是根据你自己的机器来的,目标机器上面的python路径很可能跟你这个路径对不上。咋办呢?直接指定python将行了

cd `dirname`
export PYTHONPATH=$PYTHONPATH:dep
python3 dep/bin/gunicorn -w 3 app:app

大功告成。


可以试下 appimage,一种比较通用的打包方案


方案一: 用Docker,可以自己制作一个装有Python3的环境,一个例子Dockerfile:

FROM ubuntu:latest

MAINTAINER user

ENV TIME_ZONE Asia/Shanghai

ENV APP_USER="root"

APP_HOME="/opt"

COPY ./conf/sources.list /etc/apt/sources.list

COPY ./python3_pkg.txt /tmp

RUN Deps=python3-pip tzdata net-tools

export DEBIAN_FRONTEND=noninteractive

apt-get clean

apt-get update

apt-get install -y $Deps

#设置时区

echo "${TIME_ZONE}" &> /etc/timezone

ln -sf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime

#安装python3包

pip3 install -r /tmp/python3_pkg.txt

rm /tmp/python3_pkg.txt

rm -rf /var/lib/apt/lists/*

rm -rf /root/.cache

EXPOSE 80/tcp

ENV LANG C.UTF-8

ENV LC_ALL C.UTF-8

COPY ./entrypoint.sh /sbin/entrypoint.sh

VOLUME ["/var/log","${APP_HOME}"]

ENTRYPOINT ["/sbin/entrypoint.sh"]

CMD ["app:start"]

这是我们实际项目的一个Dockerfile(有删减).

把需要安装的Python库名字,放到一个python3_pkg.txt文件里面

例如:

arrow

pycrypto==2.6.1

aliyun-python-sdk-core-v3

tornado

然后,可以把你运行的Python代码影射进去,这样就得到一个带有库的通用Python环境,这个Docker 容器可以在Linux、MacOSX、Windows下得到一样的运行环境。直接分发,打包好了,不在需要用户安装任何东西,除了Docker。目前我们自己写的Python都是这样部署的,客户只要安装了Docker,其他都不用做了。

方案2:用cython,可以把你的代码编译成一个linux下面的可执行文件,可以把第三方库都打包进去,最后只有一个文件,类似windows上面的EXE, 唯一需要依赖的是Python的so.如:libpython3.6m.so. 具体怎么做搜cython吧。很容易使用。

方案1和方案2还可以结合使用,我们实际项目里是这样的,既解决了打包问题还一定程度上加密了代码。


想打包成一个可执行文件?你需要的可能是这种工具。

makeself - Make self-extractable archives on Unix?

makeself.io

纯Python的库是比较简单的。可以把依赖库也放在同一个文件夹下面打包。可以用vritualenv或者sys.path.append的方式来引用这些库。

如果涉及到二进位的动态链接库,需要针对不同的发行版来处理,否则载入器可能会找不到一些基础库。

当然,既然是linux环境,也可以考虑Docker打包,事半功倍。

Docker Documentation?

docs.docker.com图标

如果是Ubuntu,可以考虑snap。

https://snapcraft.io/?

snapcraft.io


推荐阅读:
相关文章