python zip application

Jul 23, 2020

一直以來 Python 程式的部屬都是一件麻煩的事情。先不論 Python 版本,光是 Package 安裝就是一個災難。以前會使用 PyInstaller 但是這東西設定起來真的麻煩,並且有很多大大小小的地雷,打包時間也久,也不好 Debug 。

一直到看到了 web-greeter 才知道還有 Zip Application 種格式。這東西原理簡單,打包速度也快(就做成 zip 檔而已)。對於部屬來說,環境也只要有 python 就可以,相較於 PyInstaller 等打包 ,我覺的這是一個不錯的折衷方法。

Zip Application 的格式

  • 檔案的第一行可以是一個 shebang ( 由 #! 開頭 ),可以用來指定 interpreter
  • 一個包含著 __main__.py(這個檔案必須在 zip 檔案的最上層) 的 zip 檔
  • 原文

    範例

    我們使用 Click 這個 CLI Framework 來實做一個範例程式

  • CLI 有兩個指令 helloversion 且 package name 為 example.cli
  • 所有需要的 packages 放在目錄底下的 site-packages
  • 資料夾結構將會如下

    ├── 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"))

    加入 helloversion 兩個指令

    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 755cli 變成可執行即可

    完整的 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

    參考文件

    zipapp — Manage executable Python zip archives
    Source code: Lib/zipapp.py This module provides tools to manage the creation of zip files containing Python code, which can be executed directly by the Python interpreter. The module provides both ...
    favicon
    https://docs.python.org/3/library/zipapp.html
    zipapp — Manage executable Python zip archives
    ← Go home