zipappで作られる.pyzって具体的に何やってるの?と思って調べたことをまとめておく。
.pyzとは
Pythonとして実行できるアーカイブファイル用の拡張子。
中身はただのzipファイルだが、中に「__main__.py」を入れておくことでPythonファイルとして実行できる。
拡張子がPythonに紐付けされていればダブルクリックでも実行できるが、「python filename.pyz」で呼び出すこともできる
拡張子の例:.pyz, .pyw
zipappモジュール
フォルダと開始地点になるモジュール&関数を指定することで簡単にpyzファイルを作ってくれるモジュール。
コマンドラインからも実行でき、その場合はArgumentParserでいい感じにされてからcreate_archive関数が実行されている。
create_archiveでやっていることは指定したフォルダに「__main__.py」があればただフォルダをpyzにまとめるだけ。
ない場合は引数のmainで指定されたモジュールと関数を使って__main__.pyを作成するという処理が増える。
これによって作られる__main__.pyの中身は以下のようなエンコード指定とモジュールのインポート、関数の実行だけのシンプルなものになっている。
main引数は「module_name:function_name」の形式で指定し、内部で以下のような「__main__.py」を作成するのに使われる。
出力先はtargetで指定されたパスや名前をpathlib.Path.with_suffix(".pyz")したものになるので常にpyz形式。
# -*- coding: utf-8 -*-
import {module_name}
{module_name}.{function_name}()
pyzをzipappを使わずに作る
最終的にアーカイブの中に「__main__.py」があればいいだけなのでこのモジュールを使わずに自分でアーカイブファイルを作っても普通に動く。
元のzipapp.create_archiveはフォルダを指定して、ZipFileでまとめるというシンプルな実装。
必要なものをすべて一つのフォルダに一回まとめないと使えないので、常にこのフォルダの中身を一緒に入れたい……というような処理はそのままだとできない。
既存の関数をそのまま利用するのであれば作業フォルダを作ってそこにファイルを入れるような前処理をしてから呼び出せばそれっぽい動きはできる。
ただ結局やってることはZipFileを作っているだけなので、そのあたりは{アーカイブ時の名前: ファイルのパス}のような辞書を引数に取る形にすれば複数のフォルダから色々と引っ張ってきてpyzを作るという処理も作れるので、そのあたりを用途に合わせて自分だけのcreate_archiveっぽいものを作っても面白そう。
pyz用に処理を分岐させる
この方法で実行した場合のsys.argv[0]はアーカイブファイルのパスになっている。
zipfile.is_zipfileがpyz版での処理の分岐に使えるのでpyz用スクリプトでは使えそう。
# よくあるやつ
if __name__ == "__main__":
...
# pyzのときだけ実行させるやつ
import sys
from zipfile import is_zipfile
if is_zipfile(sys.argv[0]):
...
pyz用のスクリプトで中身を使う
sys.argv[0]をZipFileを使って取り出せば同梱したスクリプト以外のファイルも使用できる。
ZipFile.readを使って読み取れば中間ファイルなどは不要。
中に入れたcsvのデータを使うサンプル
import csv
import sys
from io import BytesIO, TextIOWrapper
from zipfile import ZipFile
with ZipFile(sys.argv[0]) as z:
# readでバイナリとして読み込んでIOでいい感じにする
stream = TextIOWrapper(BytesIO(z.read("sample.xlsx")), encoding="utf-8")
dr = csv.DictReader(stream)
for row in dr:
print(row)
その他
pyzの中のフォルダ構成はそのまま普通のワークスペースの構成と同じように解釈されるようなので、
以下のようにフォルダありの構成にしたときに「__init__.py」が正しく定義されていればlibraryフォルダをパッケージとして扱うことができる。
hoge.pyz
- __main__.py
- fuga.py
- library
-- __init__.py
-- library_module.py
パッケージを同梱できるので、ライブラリのインストールが必要だがそれ用に仮想環境を作るほどではないような小さいツールを作るときに役に立つかもしれない。
依存関係のあるライブラリも入れる必要があるので、入れたいライブラリが他にどのライブラリに依存しているかは都度調べる必要あり。
これできるかな?で触って調べてみただけなので細かい仕様等までは分からないものの、とりあえず何かに使えそう。
参考
zipappの公式ドキュメント https://docs.python.org/ja/3/library/zipapp.html