最近、Pythonで書いたちょっとしたツールとかスクリプトを、プログラミングやってない同僚とかに渡すとき、どうするのが一番いいんだろうって考えてたんだよね。正直、毎回「まずPythonをインストールして、pipでこのライブラリを入れて…」って説明するの、めちゃくちゃ面倒じゃない?
で、よく聞かれるのが「PyInstallerでexeファイルにしちゃえば?」って話。うん、それも一つの手。でもね、もっと軽くて、なんていうか…もっと「Pythonらしい」やり方があるのを知って、それが今日のテーマの「Shiv」なんだ。
正直、最初は「また新しいツールか…」って思ったんだけど、使ってみたらこれがなかなか良くて。特に、サーバー上で動かすような内部ツールとか、ちょっとした自動化スクリプトをサクッと配布したいときには、PyInstallerより便利な場面が結構あることに気づいたんだ。
で、Shivって結局なんなの?
一言でいうと、ShivはPythonプロジェクトを依存ライブラリもろとも、一つの実行可能なzipファイル(.pyzっていう拡張子になる)に固めてくれるツール。ここがポイントで、PyInstallerみたいにPythonの実行環境そのものをまるごとパッケージングするんじゃなくて、あくまでコードとライブラリだけをまとめる。
だから、出来上がるファイルがすごく軽い。その代わり、動かす環境には互換性のあるPythonがインストールされてる必要があるんだけどね。でも、開発者向けのツールとかなら、大体Python入ってるし、問題ないことが多い。
この.pyzっていうのは、Pythonが公式にサポートしてる「zipapp」っていう形式で、Pythonインタプリタが直接実行できるzipファイルのこと。知ってた?僕はこれで初めて知ったんだけど、なかなか賢い仕組みだよね。
もちろん、どんなプロジェクトでもそのまま.pyzにできるわけじゃなくて、ちょっとだけお作法がある。最低限、こんな感じの構成になってると話が早い。
myapp/(プロジェクトのルート)myapp/(こっちがPythonのパッケージ本体)__init__.py(これがないとパッケージとして認識されない、空でもOK)__main__.py(コマンドを叩いた時に最初に実行されるファイル)
setup.py(プロジェクトの情報とか、実行する関数を定義するところ)requirements.txt(使ってるライブラリの一覧、まあ、これは任意)
要するに、Shivに「このプロジェクトを実行するってなったら、どのファイルのどの関数を呼べばいいの?」ってのを教えてあげる必要があるってこと。そのためにsetup.pyがめちゃくちゃ大事になってくるんだ。
じゃあ、実際にどうやって作るの?
じゃあ、手を動かしてみよう。思ってるより簡単だから。プロジェクトの準備ができてるなら、ほんの数ステップで終わる。
まず一番大事なのが、さっきも言ったsetup.py。ここで「エントリーポイント」っていうのを定義する。要は、このプログラムを実行するときのコマンド名と、それに対応する関数を結びつける作業だね。
# setup.py の中身
from <a href="https://packaging.python.org/en/latest/tutorials/packaging-projects/" target="_blank" class="blogHightLight_css nobox">setuptools</a> import setup, find_packages
setup(
name="myapp",
version="0.1",
packages=find_packages(),
entry_points={
'console_scripts': [
'myapp = myapp.__main__:main'
]
},
install_requires=[
'requests', # 例えばrequestsライブラリに依存してるとか
'beautifulsoup4',
],
)
この'myapp = myapp.__main__:main'っていう部分がキモ。myappっていうコマンドを叩いたら、myappパッケージの中の__main__.pyファイルにあるmain()関数を実行してね、っていう意味。コロン(:)でモジュールと関数を区切るのがsetuptoolsの書き方なんだ。
で、もちろん、呼び出される側の__main__.pyもちゃんと用意しないとダメ。
# myapp/__main__.py の中身
def main():
print("Shivでパッケージ化されたアプリへようこそ!")
# ここに実際の処理を書く
ここまでできたら、あとはターミナルでShivコマンドを一発叩くだけ。
# カレントディレクトリをパッケージングする場合
shiv -c myapp -o myapp.pyz .
それぞれのオプションの意味はこんな感じ。
-c myapp:実行するコンソールスクリプトを指定。setup.pyで定義したやつね。-o myapp.pyz:出力するファイル名。まあ、これは見ての通り。.:パッケージ化する対象のディレクトリ。この場合はカレントディレクトリ。
もしrequirements.txtで依存関係を管理してるなら、それを使うこともできる。こっちのほうがsetup.pyがスッキリして個人的には好きかな。
# requirements.txt を使う場合
shiv -c myapp -o myapp.pyz -r requirements.txt .
これでmyapp.pyzっていうファイルができあがる。あとはこれを実行するだけ。Unix系(MacとかLinux)なら実行権限をつけて、
chmod +x myapp.pyz
./myapp.pyz
Windowsなら、
python myapp.pyz
で動く。簡単でしょ?これで仮想環境がどうとか、pip installがどうとか言わずに済む。
PyInstallerじゃダメなの?Shivとどっちがいい?
ここで、みんなが思う疑問。「で、結局PyInstallerとどっちがいいの?」って話。これはもう、完全に「場合による」としか言えないんだけど、判断するための比較表を作ってみた。僕の個人的な感想も入ってるけどね。
日本ではPyInstallerがすごく人気で、ググれば日本語の情報がたくさん出てくるよね。でも、ShivはもともとLinkedInが社内ツールを配布するために作ったもので、そういう「開発者が開発者のために」使うシナリオにすごく強い。海外のテック企業のこういう動きを見てると、PyInstallerとは違う設計思想があるのがわかって面白い。公式のGitHubリポジトリを見ると、その背景がよくわかるよ。
| 比較ポイント | Shiv (.pyz) | PyInstaller (.exe / バイナリ) |
|---|---|---|
| 配布物のサイズ | めっちゃ軽い。コードと依存関係だけだから。数十KBとか数百KBで済むことも。 | 重い…。Pythonインタプリタごとだからね、仕方ないけど。簡単なものでも数十MBになるのはザラ。 |
| 実行環境 | Pythonのインストールが必須。バージョン互換性も気にする必要あり。 | Python不要。完全に自己完結してるから、誰にでも渡せる。これが最大の強み。 |
| ビルド時間 | 一瞬。ほんとに。ファイルをzipに固めてるだけに近いから。 | まあまあ時間かかる。依存関係を解析して、実行ファイルを構築するから、プロジェクトが大きくなるとコーヒー一杯飲めるくらい待つ。 |
| ユースケース | 開発チーム内のツール配布、CI/CDでの利用、サーバー上のスクリプト実行とか。 | エンドユーザー(非開発者)向けのデスクトップアプリ配布。とにかくダブルクリックで動いてほしい場合。 |
| Windows対応 | .pyzはネイティブじゃないから、python myapp.pyzって一手間いる。まあ、ラッパー作ればいいんだけど。 |
完璧。.exeが作れるから、Windowsユーザーには一番親切。 |
要するに、渡す相手がPCに詳しくて、Python環境がある程度整ってるならShivのほうが手軽で速い。逆に、PCに詳しくない人に「このファイル、ダブルクリックして」で終わらせたいならPyInstaller一択、って感じかな。トレードオフだね。
Windowsユーザー向けにexeっぽく見せる方法
さっきの表でもちょっと触れたけど、「Shivはいいけど、Windowsユーザーにpython myapp.pyzって打ってもらうのはちょっと…」って思うよね。わかる。でも、これもちゃんと解決策がある。
一番簡単なのは、バッチファイル(.bat)を一緒に渡す方法。メモ帳でこんなファイルを作るだけ。
REM launch_myapp.bat
@echo off
python myapp.pyz %*
このlaunch_myapp.batとmyapp.pyzを同じフォルダに入れておけば、ユーザーはバッチファイルをダブルクリックするだけで実行できる。うん、これで十分なことも多い。
もうちょっとスマートにやりたいなら、PyInstallerを「ラッパー」として使うっていう合わせ技もある。まず、こんな感じの超短いPythonスクリプト(例えば launcher.py)を作る。
# launcher.py
import runpy
runpy.run_path("myapp.pyz", run_name="__main__")
やってることは、ただmyapp.pyzをPythonで実行してるだけ。で、このlauncher.pyをPyInstallerで固める。
pyinstaller --onefile launcher.py
そうするとdistフォルダにlauncher.exeができる。これをmyapp.exeとかに名前を変えて、myapp.pyzと一緒に配布すれば、ユーザーは.exeをダブルクリックするだけで、実質的にShivのパッケージが動く、っていう仕組み。ちょっとトリッキーだけど、見た目は完全にネイティブアプリになる。
これ、よくハマるから気をつけて
最後に、僕がShivを使い始めて「うわっ」ってなった、よくある失敗例をいくつか共有しておく。これを知ってれば、無駄な時間を溶かさずに済むはず。
entry_pointsのタイプミス:setup.pyの'myapp = myapp.__main__:main'の部分、一文字でも間違ってると「そんなコマンドないよ」って言われて動かない。特にファイルパスとか関数名を間違えやすい。ビルドは成功しちゃうからタチが悪いんだよね。__init__.pyの存在を忘れる:サブディレクトリに処理を分けてる場合とか、各ディレクトリに__init__.pyファイルを置かないと、Pythonがそれをパッケージとして認識してくれない。結果、ImportErrorになる。基本だけど、つい忘れがち。- パッケージ化するパスの間違い:
shiv ... .の最後の「.」を忘れたり、違うディレクトリで実行したりすると、意図しないファイル構成になって実行時に「ファイルが見つかりません」ってなる。ちゃんとプロジェクトルートで実行するのが大事。 - 大事な情報をハードコーディングしちゃう:これはShivに限った話じゃないけど、配布が簡単になる分、うっかりAPIキーとかパスワードをコードに直接書いたままパッケージ化しちゃうと大事故になる。Shivはただのスーツケース。金庫じゃないからね。大事な情報は環境変数とか、別の設定ファイルから読み込むようにしよう。本当に。
まあ、だいたいこんな感じかな。Shivは、Pythonのエコシステムにうまく乗っかった、すごくクレバーなツールだと思う。全部をexeにするんじゃなくて、こういう選択肢があるって知っておくだけでも、Pythonでの開発がまた一つ面白くなるんじゃないかな。
あなたが今作ってるツールなら、ShivとPyInstaller、どっちのほうが向いてると思う? もしよかったら、理由も一緒にコメントで教えてくれると嬉しいな!
