はじめに
dashで作成したアプリをpyinstallerを使ってPythonファイルをexe化しようとしたとき、画像リソースや依存パッケージが読み込まれなかったため手動でspecファイルを編集したときの備忘録です。
dashとは・・・
Pythonだけで良い感じにかっこいいダッシュボードが作れるライブラリです。
プロトタイプ開発には非常に適していると思います。
Dash Enterprise App Gallery
サーバーを公開して使用してもらうのが一般的な使い方ですが、今回はテスト環境をプログラマではない人に配布する目的でexe化しました。
手順
step1. minimumな仮想環境を作成する
pyinstallerは導入しているライブラリをすべて合わせてexe化しようとするので最初に環境をキレイにします。導入するライブラリはアプリで使用する最低限のものにしてします。ここでいらないライブラリまで仮想環境に含めるとアプリの容量が無駄に大きくなります。
このとき、仮想環境はアプリが置かれたディレクトリと同じ階層に作成しておくとspecファイル内部で相対ファイルパス指定が楽にできます。
python3 -m venv python37
.\python37\Scripts\activate
. python37/bin/activate
step2. 必要なライブラリ及びpyinstallerをインストールする
作成した仮想環境に必要なライブラリ及びpyinstallerをインストールします。
pip install 必要なライブラリ
pip install pyinstaller
step3. specファイルを作成する
dashアプリの起動ファイルとなるapp.pyと同じ階層にspecファイルを用意します。
pyinstallerにコマンドライン引数としてspecファイルを渡すため、名前はなんでも良いです。(仮にdashapp.specとします。)
pyinstaller -D app.py
を実行することにより、たたき台となるspecファイルを自動的に生成することもできます。
ただしdashアプリの場合は依存モジュールの設定が自動では含まれないためbuildには失敗します。
(-Dオプションはonedirモードでexeを作成する場合に指定する。onefileモードであれば不要。)
block_cipher = None
a = Analysis(['app.py'],
pathex=['.'],
binaries=[],
datas=[
('./image','image'),
('./python37/lib/python3.7/site-packages/dash_core_components', 'dash_core_components'),
('./python37/lib/python3.7/site-packages/dash_html_components', 'dash_html_components'),
('./python37/lib/python3.7/site-packages/dash_renderer', 'dash_renderer'),
('./python37/lib/python3.7/site-packages/dash_table', 'dash_table'),
('./python37/lib/python3.7/site-packages/dash_bootstrap_components', 'dash_bootstrap_components'),
('./python37/lib/python3.7/site-packages/plotly', 'plotly'),
],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='dashapp',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='dashapp')
# OSX用
# app = BUNDLE(coll,
# name='dashapp.app',
# icon=None,
# bundle_identifier=None)
ポイント
前述したとおり、pyinstaller -D app.py
でexeを作成すると不足しているモジュールがあると怒られます。
自動的にパスを取得できない依存ライブラリがある旨のエラーが出力されるため、これらを手動でdatasに記述していきます。
step2で作成した仮想環境に不足しているライブラリが存在するため、相対パスで指定します。
私の環境で不足していたライブラリは以下でした。
- dash_core_components
- dash_html_components
- dash_renderer
- dash_table
- dash_bootstrap_components
- plotly
また、画像ファイル等、リソースファイルも手動で追加する必要があります。
画像ファイルを配置しているディレクトリごと相対パスで指定します。
4. pyinstallerを実行する
pyinstaller dashapp.spec
specファイルが適切に作成されていれば、distファイル下にdashappフォルダが出来上がります。
windows環境の場合はdashappフォルダ下にexeファイルが存在します。
Mac環境の場合はapp = BUNDLE...
以下のコメントを外して実行してください。
dashapp.appが出来上がります。
余談
onedirモードで実行する理由
ネットで検索するとonefileモードで実行している人が多いです。onefileモードはすべてのバイナリをexeでパッケージ化して使用するモードで、1ファイルにまとまるため見た目がすっきりしますが起動時にすべてのバイナリをtempファイルに展開するため起動が非常に遅くなります。
特にdashアプリは使用しているライブラリの数も多いため、onefileモードは不向きなように思います。
補足ですが、onefileモードで作成する場合はexclude_binaries=True,
あたりの記述が変わるため、一度pyinstaller app.py
でspecファイルを作成し直してから編集すると良いと思います。
実行ファイルのカレントディレクトリ
exe化したファイルを直接実行した場合、Windows環境とMac環境では実行時のカレントディレクトリが異なるため注意が必要です。
- Windowsはexeを実行すると、exeファイルが置かれているフォルダがカレントになります。
- Macは出力されたUnix実行ファイル(.app)を実行すると、HOMEディレクトリがカレントになります。
クロスプラットフォーム対応をする場合は気を付けてください。