如題,找了好久,沒有答案,都是打包成.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


推薦閱讀:
相關文章