- Python で書いたスクリプトを配布したい!
- PyInstaller をマスターしたい!
このような方に向けて書きました。
頑張って開発したアプリケーションを他の人に使っていただくときに、通常のアプリケーションのようにダブルクリックで起動する「実行ファイル」にしておくと親切です。
Pyhthon の実行ファイル化ツールで最も利用されているのは PyInstaller です。
初見ではオプションの指定方法や.spec
ファイルの書き方など、なかなか理解できない箇所も多くあります。
本記事では「これから PyInstaller で実行ファイル化したい!」という方に向けて、必要な知識や実行ファイル化の手順をわかりやすく解説します。
PyInstaller の概要を解説
まずは、PyInstaller を使うために必要な前提知識からご紹介します。
PyInstallerの概要
PyInstaller は Python プログラムを「実行ファイル」に変換するツールです。
Windows, Mac, Linux に対応しています。
詳しくは後述しますが、.spec
ファイルでビルドする際の挙動を調整できたり、細かい設定が効くので、配布用に変換するには絶好のツールです。
ただし、出力される実行ファイルの容量は大きくなりがちなのがデメリットではあります。
他のパッケージングツールと比較
他のパッケージングツールとの比較を表にまとめました。
ツール名 | 単一ファイル化 | クロスプラットフォーム | ファイルサイズ | ビルド時間 |
---|---|---|---|---|
PyInstaller | ○ | ○ | × | △ |
py2exe | × | ×(Winのみ) | △ | ○ |
cx_Freeze | × | ○ | × | ○ |
py2app | × | ×(Macのみ) | △ | ○ |
nuitka | ○ | ○ | ○ | × |
海外ではnuitka
が使われることも多いようですが、設定が複雑なのでちょっと使いづらかったです。
やっぱりPyInstaller
が総合してバランスが取れていると思います。
実行ファイルの構成
PyInstaller 実行後には、以下が出力されます。
- dist/: 実行ファイルを格納
- build/: ビルドの一時ファイルを格納
- myscript.spec: ビルド内容に関する設定ファイル
具体的には以下のような構成です。
my_project/
├── my_script.py
├── my_script.spec
├── build/
│ └── my_script/
│ ├── base_library.zip
│ ├── my_script.exe.manifest
│ ├── my_script.exe.pyi
│ ├── warn-my_script.txt
│ └── <その他の中間ファイル>
└── dist/
└── my_script/
├── my_script.exe
├── <依存ファイル1>
├── <依存ファイル2>
└── <その他の依存ファイル>
.spec
ファイルについては、本記事の後半で詳しく解説します。
PyInstaller で実行ファイル化するまでの流れ
PyInstaller は Python プログラムをスタンドアロンの実行可能なファイル(.exe、.appなど)に変換するツールです。実行ファイル化には、以下の手順で行います。
- インストール
- Python スクリプトを用意する
- PyInstaller の実行
順番に解説します。
インストール
まずはお使いの環境に Python がインストールされているか確認します。
python --version
# 結果
# Python 3.11.7
バージョンが帰ってくれば、Python がインストールされているということなので次に進みましょう。
続いて、PyInstaller のインストールをします。
pip install pyinstaller
念の為以下のコマンドで、正常にPyInstaller がインストールされたか確認しましょう。
pyinstaller --version
# 結果
# 6.6.0
バージョンが返ってくればインストールは成功です!
Python スクリプトを用意する
ごく簡単なスクリプトとしてmyscript.py
を作ります。
print("hello, world")
次の手順で、このスクリプトを実行ファイル化していきます。
PyInstaller の実行
コマンドラインから、以下のコマンドを実行します。
pyinstaller myscript.py
dist
フォルダとbuild
フォルダが作成されて、dist
フォルダ内に実行ファイルが生成されます。
ちなみにコマンドにはオプションを設定することができます。
オプション | 内容 |
---|---|
--onefile | 単一の実行ファイルにまとめる |
--noconsole | コンソールウィンドウを表示しない |
--icon | 実行ファイルのアイコンを指定する |
--clean | キャッシュを削除する |
上記のオプションを使った場合のコマンド例はこちら。
pyinstaller --onefile --noconsole --icon=my_icon.ico --clean myscript.py
その他のオプションについては、公式ドキュメントをご参照ください。
» 参考:Using Pyinstaller – Options
実行方法
dist
フォルダ内に生成された実行可能ファイルを実行します。
./dist/myscript
これで先ほどのmyscript
が実行され、Hello, World!
の文字列が表示されるようになります。
Hello, World!
Saving session...
...copying shared history...
...saving history...truncating history files...
...completed.
[プロセスが完了しました]
今回の場合にはコンソール上にprint()
するだけなので、実行ファイル化する際には--noconsole
を入れないようにしましょう。
入れてしまうとprint()
関数の結果が見られないためです。
spec ファイルを使ったビルド
PyInstaller では--noconsole
などのオプションをつけてビルドを行うものが主流ですが、.spec
ファイルでビルド内容を定義することができます。
メリットは次のとおり。
- 複雑な設定の管理
- 再利用性が向上
- バージョン管理できる
何度もオプションを書いてビルドし直す、という面倒な作業がなくなります。
以下、詳細を見ていきましょう。
spec ファイルの生成方法
.spec
ファイルは、通常のビルドコマンドを実行すると生成されます。
pyinstaller myscript.py
myscript.specというファイルが出力されるので、内容を確認していきましょう。
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['myscript.py'],
pathex=['/path/to/your/script'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='myscript',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='myscript',
)
specファイルを使ったビルド
.spec
ファイルを使ってビルドする場合には、以下のコマンドを実行します。
pyinstaller myscript.spec
これで.spec
ファイルの内容をもとに実行ファイルが出力されます。
.spec ファイルのカスタマイズ
.spec
ファイルをカスタマイズして、ビルドの内容を変更する方法をご紹介します。
- 単一ファイル出力
- コンソールの非表示
- アイコンの設定
単一ファイル出力
onefile
オプションのように、単一ファイル出力する方法です。
EXE
関数のexclude_binaries
をTrue
COLLECT
ステップを削除
以下のように、.spec
ファイルを編集します。
exe = EXE(
・・・
exclude_binaries=True, # onefileオプション
・・・
)
# onefileのためにCOLLECTステップを削除
仮にexclude_binaries=False
とした場合、アプリの受付部分にあたる「実行ファイル」と実際のロジックを担当する「バイナリ」に分割されます。
分割することで「各ファイルを独立して管理」できたり「ビルド時間やファイルサイズが小さくなる」というメリットもあります。
ただし、配布時にはわかりやすさ重視でonefile
オプションをつけることのほうが多いです。
コンソールの非表示
--noconsole
オプションと同じ設定を行います。
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='myscript',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False, # noconsole オプション
)
これでコンソールが非表示になります。GUIアプリなどはこの設定が良いです。
逆に、あえてコンソールを表示させたい場合にはconsole=True
を指定しましょう。
アイコンの設定
EXE
セクションでicon
パラメータを追加します。
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='myscript',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
icon='path/to/icon.ico'
)
デバッグとトラブルシューティングの方法
PyInstaller でよくあるエラーと対処法をご紹介します。
また、実行ファイル化後のトラブルについても具体的な対策に触れていきましょう。
ModuleNotFoundError
モジュールがビルド対象から外れてしまった場合に表示されるエラーです。
ModuleNotFoundError: No module named 'some_module'
この場合、.spec
ファイルのhiddenimports
にモジュールを追加します。
# .specファイルの編集
a = Analysis(['my_script.py'],
pathex=['/path/to/my_project'],
hiddenimports=['some_module'], # 見つからなかったモジュールを指定
...)
ImportError
外部ライブラリのインポートに失敗した場合のエラーです。
ImportError: DLL load failed: The specified module could not be found.
.spec
ファイルのbinaries
にライブラリのパスを追加します。
# .specファイルの編集
binaries=[('path/to/dll_or_shared_library', '.')]
FileNotFoundError
指定されたファイルパスが間違っている場合のエラーです。
FileNotFoundError: [Errno 2] No such file or directory: 'some_file'
datasにデータファイルの絶対パスを追加します。
# .specファイルの編集
datas=[('path/to/datafile', 'destination_directory')]
デバッグモードの使用
PyInstaller のデバッグモードを使うことで、詳細なログを見ることができます。
pyinstaller --onefile --debug=all my_script.py
デバッグモードには二種類あります。
all
: すべてのデバッグ情報を有効にします。noarchive
: アーカイブファイルを生成せず、すべてのファイルを展開された状態で保持します。これにより、ファイルの読み込みエラーを容易に特定できます。
これにより、コンソール上に詳細なログが表示されるようになります。
どこで問題が起きたかがわかるので、とても便利です。
Python スクリプト内でログ出力する
アプリケーション実行中のエラーは、スクリプト内にログ出力するようにすることでも対応できます。
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("This is a debug message")
※ ログ出力の方法については、後日別記事でご紹介する予定です。
【アドバンス】PyInstaller が行う処理の流れ
spec
ファイルを読み前提知識として、PyInstaller の処理の流れを理解しておきましょう。
以下の順番で処理が流れていき、実行ファイルが生成されます。
- ファイルを分析
- PYZ オブジェクトを作成
- EXE オブジェクトを作成
- すべてのファイルを統合
順番に解説していきます。
ファイルを分析
実行ファイル化したい Python スクリプトを含む、すべてのファイルを調べるフェーズです。
.spec
ファイルでは、Analysis
オブジェクトとして表現されてます。
a = Analysis(
['myscript.py'], # 対象の Python スクリプト名
pathex=['/path/to/your/script'], # スクリプトのパスを指定
binaries=[], # 追加のバイナリファイル(DLL, SOファイル)
datas=[], # 追加のデータファイル(画像、設定ファイルなど)
hiddenimports=[], # PyInstaller が自動検出できないモジュールのリスト
hookspath=[], # カスタムフックファイル
runtime_hooks=[], # アプリ実行時に実行されるスクリプトのリスト
excludes=[], # PyInstaller に含めないモジュールリスト
)
この場合には、/path/to/your/script
からmyscript.py
を探して、これを実行ファイル化します。
PYZ オブジェクトを作成
続いては、Analysis で対象とされた Python ファイルをアーカイブにまとめます。
pyz = PYZ(a.pure, a.zipped_data)
a.pure
はAnalysis
で取得された純粋な Python スクリプトのリストです。
a.zipped_data
はそれ以外のファイル群で、Python モジュールが依存している圧縮ファイルなどが指定されます。
EXE オブジェクトを作成
実際の実行ファイルを作成するステップです。
exe = EXE(
pyz, # 一つ前のステップで作成した PYZ オブジェクト
a.scripts, # Analysis オブジェクトで解析されたスクリプト
[], # 追加の Python スクリプトを指定する場合に必要
exclude_binaries=False, # バイナリと実行ファイルを分離しない
name='myscript', # 実行ファイルの名前
)
すべてのファイルを統合
COLLECT
では、Analysis
とEXE
をまとめて「最終的なディレクトリ構造」を作成します。
coll = COLLECT(
exe, # 先に作成したEXEオブジェクト
a.binaries, # 解析されたバイナリのリスト
a.zipfiles, # 解析された圧縮ファイルのリスト
a.datas, # 解析されたエータファイルのリスト
strip=False, # デバッグ情報等を含めない
upx=True, # True にすると UPX で圧縮
name='myscript', # 出力ディレクトリ名を指定
)
onefile
オプションをつけた場合には、COLLECT
ステップがありません。
そのため、EXE
には以下のようになっているはずです。
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True, # これがonefileに相当
name='myscript',
)
補足:こんなときにどうする?
PyInstallerを使っていると、たまに問題に出くわすことがあります。
このセクションでは、Q&A形式でお答えしていきます。
- インポートしたはずのモジュールがなぜかビルドされていない
-
通常はインポートされたモジュールも一緒にビルドされますが、PyInstaller が自動的に認識できない場合もあります。
このような場合には、追加されないモジュールを明示します。
.spec
ファイルで指定する場合には、以下になります。a = Analysis( ・・・ hiddenimports=['module1', 'module2'], # ここに隠しインポートを追加 ・・・ )
オプションで指定するなら、以下です。
pyinstaller --hidden-import=module1 --hidden-import=module2 myscript.py
これで、PyInstaller の解析で拾いきれなかったモジュールもビルド対象に含まれます。
- セキュリティ対策はどうしたら良い?
-
PyInstaller でできるセキュリティ対策には、例えば以下があります。
- UPX圧縮の使用
- 難読化ツールの使用
- 機密情報は環境変数から読み込む
UPX圧縮については
.spec
ファイルのEXE
で指定できます。これによって一応は解析が難しくなりますが、万全な対策ではありません。より高度な難読化については、
PyArmor
やPyObfuscate
を検討してみてください。また機密情報を含めると漏洩リスクがあるので、以下のように環境変数から読むと安全性が格段に高まります。
import os api_key = os.getenv("API_KEY")
- Mac でビルドした実行ファイルが Windows で実行できない
-
使いたい OS と同じ OS でビルドしなければなりません。
つまり、以下のようになります。
- Windows用の実行ファイルはWindows上でビルド
- macOS用の実行ファイルはmacOS上でビルド
- Linux用の実行ファイルはLinux上でビルド
もし配布先のOSと作業中のPCのOSが異なる場合には、配布先の OS と同じDocker イメージをビルドして実行ファイル出力させます。
例えば Mac 上で Windows 用の実行ファイルを出力する場合には以下です。
# 基本イメージとしてWindows Server Coreを使用 FROM mcr.microsoft.com/windows/servercore:ltsc2019 # Pythonをインストール RUN powershell -Command ` Invoke-WebRequest -Uri https://www.python.org/ftp/python/3.9.1/python-3.9.1-amd64.exe -OutFile python.exe; ` Start-Process python.exe -ArgumentList '/quiet InstallAllUsers=1 PrependPath=1' -NoNewWindow -Wait; ` Remove-Item -Force python.exe # 必要なパッケージをインストール RUN pip install pyinstaller # 作業ディレクトリを設定 WORKDIR /app # ローカルのファイルをコンテナにコピー COPY . /app # PyInstallerを使用して実行ファイルを生成 RUN pyinstaller --onefile my_script.py # 最終的な実行ファイルを出力ディレクトリにコピー CMD ["powershell", "Copy-Item", "C:\\app\\dist\\my_script.exe", "C:\\output"]
Linux の場合も同様に行うことができます。
ただし Docker イメージには Mac OS がないので、配布先が Mac の場合だけは Mac OS で実行ファイルをビルドする必要があります。
まとめ
PuInstaller は、以下の内容を理解しておくと全体像が把握しやすいです。
- 処理の流れ(Analysis => PYZ => EXE => COL)
.spec
ファイルの内容理解- デバッグの方法
- 他のOSに配布する際のテクニック
ぜひ、参考にしてみてください。
コメント