python zip application
Jul 23, 2020
一直以來 Python 程式的部屬都是一件麻煩的事情。先不論 Python 版本,光是 Package 安裝就是一個災難。以前會使用 PyInstaller 但是這東西設定起來真的麻煩,並且有很多大大小小的地雷,打包時間也久,也不好 Debug 。
一直到看到了 web-greeter 才知道還有 Zip Application 種格式。這東西原理簡單,打包速度也快(就做成 zip 檔而已)。對於部屬來說,環境也只要有 python 就可以,相較於 PyInstaller 等打包 ,我覺的這是一個不錯的折衷方法。
Zip Application 的格式
範例
我們使用 Click 這個 CLI Framework 來實做一個範例程式
資料夾結構將會如下
├── example │ └── cli │ ├── helloworld.py │ └── version.py ├── __main__.py └── site-packages
安裝 Click package 到 site-packages
pip 指令加上 --target 就可以指定 package 的安裝目錄,因此我們可以使用
pip install --target ./site-packages click
執行完畢之後應該就會在 site-packages 裡面看到 click 的資料夾了
__main__.py
在 __main__.py 裡面,需要手動將 site-packages 加入 sys.path 裡面,這樣這些 packages 才會被搜尋到。
import pathlib import sys import os # 取得當前路徑 CURRENT_DIR = pathlib.Path(__file__).parent.absolute() # 將當前路徑加入 sys.path sys.path.append(CURRENT_DIR) # 將 site-packages 加入 sys.path sys.path.append(os.path.join(CURRENT_DIR, "site-packages"))
加入 hello 與 version 兩個指令
import click # nopep9 from example.cli.version import version_group # nopep8 from example.cli.helloworld import hello_group # nopep8 cli = click.CommandCollection(sources=[hello_group, version_group]) if __name__ == "__main__": cli()
helloworld.py
import click @click.group() def hello_group(): pass @hello_group.command() def hello(): click.echo("hello world !")
version.py
import click @click.group() def version_group(): pass @version_group.command() def version(): click.echo("Version: 1.0.0")
測試一下
寫好上面的檔案之後,可以直接用 python 執行一下 __main__.py 測試是否運作正常
> python __main__.py Usage: __main__.py [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: hello version
打包
使用 zip 指令
zip -r cli.zip example site-packages __main__.py
使用 python zipfile package
有時候打包的環境沒有安裝 zip ,那麼也可以使用 python 內建的 zipfile package
建立一個 zip.py 檔案,內容如下
import os import sys import zipfile def add_dir_to_zip(path, ziph): # ziph is zipfile handle for root, dirs, files in os.walk(path): for file in files: file_path = os.path.join(root, file) print(file_path) ziph.write(file_path) def add_file_to_zip(path, ziph): ziph.write(os.path.join(path)) if __name__ == '__main__': if len(sys.argv) < 2: print("Please input the file name") exit(1) filename = sys.argv[1] print("Creating {}.zip".format(filename)) zipf = zipfile.ZipFile('{}.zip'.format(filename), 'w', zipfile.ZIP_DEFLATED) add_dir_to_zip('example', zipf) add_dir_to_zip('site-packages', zipf) add_file_to_zip('__main__.py', zipf) zipf.close()
要打包的時候,執行 python zip.py cli.zip 就會跟上面的 zip 指令有一樣的效果。
加入 shebang
echo '#!/usr/bin/env python' > cli cat cli.zip >> cli
最後使用 chmod 755 讓 cli 變成可執行即可
完整的 build script
#!/bin/bash zip -r cli.zip example site-packages __main__.py # 或者使用 # python zip.py cli.zip echo '#!/usr/bin/env python' > cli cat cli.zip >> cli chmod 755 cli
測試看看打包出來的 cli
❯ ./cli Usage: cli [OPTIONS] COMMAND [ARGS]... Options: --help Show this message and exit. Commands: hello version ❯ ./cli hello hello world ! ❯ ./cli version Version: 1.0.0