python

pythonプログラムを配布するために.exe化する手順

2020-06-13

先日、いいプログラムを作ることができ、そこに需要もあることが判明したので、pythonで作ったプログラムを配布しました。

その時に".py"の状態から”.exe”にビルドして他のマシンでも動作するようにするという部分で苦戦したのでシェアしておきます。

ヂまるの環境

  • Windows 10 Home 64bit
  • python 3.8.2

Pyinstallerを使えば一つの.exeにビルドできる

pythonで作成したプログラム(.py)を実行ファイル(.exe)にするにはPyInstallerというライブラリを使います。

ということで、この記事はPyInstallerのスタートアップガイド兼ハマりポイントの注意喚起のような構成になっています。

最短で実行ファイル(.exe)を作成し、ハマりポイントを確認しておきたいという方は参考になると思います。

まずはサンプルプロジェクトを準備

今回は、こんな感じでHelloPyinstallerプロジェクトを作成し、その中にhello.pyプログラムが1つだけある、といった感じのサンプルプロジェクトを作成しました。

HelloPyinstaller
 |-hello.py
print('ケンヂまるですヨロシク')
# プログラムが終了してもシェルが閉じないようにする目的
input()

PyInstallerをインストール

続いて、PyInstallerをインストールしてビルドします。

> pip install pyinstaller
> pyinstaller --onefile hello.py

するとプロジェクトフォルダの中に色々とファイルやフォルダが出来上がりました。
代表的なものだけ表すと

1HelloPyinstaller
 |-build        ← 自動生成された。使い道不明。
 |-dist         ← 自動生成された。使い道不明。
   |-hello.exe  ← 念願の実行ファイルが生成された
 |-hello.py
 |-hello.spec   ← 生成された、PyInstallerビルド用設定ファイル

hello.exeが生成されました!

ポイント

> pyinstaller hello.py

のように、--onefileオプションをつけていない場合、distフォルダの下に.pyなど様々なファイルを集められてきます。
そしてそれらも同梱しないと動作しません。

配布パッケージという想定なので、ダブルクリックして実行してみましょう…。

おお!!
動いてますね!

ファイルサイズはimport文に比例して大きくなる

PyInstallerで見事に実行ファイルを作成することができたわけですが、そのサイズは8MBほどでした。

そこに、pandasをインポートするだけの文を追加してみるとどうなるでしょうか?

import pandas as pd

print('ケンヂまるですヨロシク')
input()

結果は、実行ファイルのサイズは25MBほどまで膨れ上がりました。

PyInstallerで作成した実行ファイルのサイズ(25.4MB)

つまり

import pandas as pd

という1行を加えるだけで、20MB近くもサイズが膨れ上がったことになります。
残念なことに、例えば

from pandas import DataFrame

のように、限定的にインポートするような書き方でも、実行ファイルのサイズは変わりません。

また、venvなどを用意して最小環境からビルドすると実行ファイルのサイズが小さくなる、という記事もありますが、この問題は既に解決していて、importしていないモジュールは読み込まないようになったようです。

>> 【悲報】PyInstallerさん、300MBのexeファイルを吐き出すようになる

【あるある】開発PCでうまくいっても他のPCではうまくいかない

さて、実行ファイルができた!バンザイ!

と思われているかもしれませんが、ちょっと待ってください。
まだやることは残っています。

動作確認作業です。

実際、PyInstallerでビルドした実行ファイルは、他のPCで実行しようとするとエラーになって動かないことが多いんです。
それで僕もしょっぱなハマりました。

そのため、PyInstallerで.exe化したプログラムは、python環境がインストールされていない他の人のPCで一度試すようにしましょう。
友達がいないって方は、VMwareなどの仮想環境上のWindowsで試してみて下さい。

ちなみに僕は、VMwareで試したクチです。

VMware環境を準備する

なぜ、VMwareなのか?

仮想環境としてはVMwareの他にもVirtualBoxなど、いくつか選択肢があるように思えますが、2020年現在の世界的なシェアとしてはVMwareがダントツだからです。

VMWare 製品のダウンロードページから「VMware Workstation Player」をダウンロード・インストールします。

インストール途中の「拡張キーボード ドライバ」にはチェックをつけたほうがよさそうです。(テキトーに言ってます)

お金を払わないなら、非営利目的で使って下さいねっと。
(僕はPyInstallerの使い方の研究目的です)

仮想マシンのダウンロード

通常、仮想環境だとしてもWindowsをインストールするとなるとライセンスを買わなくてはなりません。
しかし、動作確認目的に使っていいよと公開された仮想環境があるので、それを使えばお金をかけずに検証可能です。

それがこちら→Windows 10 の開発環境を取得する

こちらからVMwareの仮想マシンをダウンロードします。

仮想マシンを起動

ダウンロードが終わったら、先ほどインストールしたVMWareを起動しましょう。

このアイコンですね。

ダウンロードしたovfファイルを選択

ダウンロードしたバーチャルマシンの名前からVMWareで管理する名前に変更

すぐに再生できる。

Windowsのデスクトップが表示される。

おおおこんなにすんなりお金をかけずにテスト環境が構築できるなんて!

ファイルはドラッグ&ドロップで中にバーチャルマシン環境に出し入れ可能だし、かなり直感的に操作できるぞい!
(10年前はこうはいっていなかった。。技術進化ハンパないなー)

PyInstallerでビルドした.exeを仮想マシン内で実行したらエラーになった

最初に作ったhello.pyのような、ライブラリを何もインポートしていないプログラムであれば、ほぼ問題は起こりません。

でも、色々なライブラリをインポートして使っているプログラムだと、PyInstallerでビルドした .exe はかなりの確率でエラーになります。

僕の場合は、pandasライブラリを使ってCSVファイルを変換するプログラムを検証中にエラーに。

--noconsoleをつけないでビルドしている場合はこんな感じ

Traceback (most recent call last):
  File "PyInstaller\loader\rthooks\pyi_rth_pkgres.py", line 13, in <module>
  File "PyInstaller\loader\pyimod03_importers.py", line 623, in exec_module
    exec(bytecode, module.__dict__)
  File "lib\site-packages\pkg_resources\__init__.py", line 86, in <module>
ModuleNotFoundError: No module named 'pkg_resources.py2_warn'
[10672] Failed to execute script pyi_rth_pkgres

--noconsoleつけてビルドしている場合はこんなダイアログが

こうなってしまった場合は、ビルド時に生成された.specファイルのhiddenpackagesに適切なパッケージを指定してやると動作するようになります。

HelloPyinstaller
 |-build        ← 自動生成された。使い道不明。
 |-dist         ← 自動生成された。使い道不明。
   |-hello.exe  ← 念願の実行ファイルが生成された
 |-hello.py
 |-hello.spec   ← 生成された、PyInstallerビルド用設定ファイル

※6行目のhello.specが今回作成されたspecファイル

適切なパッケージというのは、エラーメッセージでNo module named 'モジュール名'のような感じで言われている部分のことです。

つまり、エラーのダイアログだけからではhiddenpackagesに追加するべきモジュールがわからないので、確認が終わるまでは--noconsoleは指定せずにビルドしましょう。

最初に作成したHelloPyinstallerプロジェクトのhello.pyに話を戻すと、対応後のhello.specファイルはこんな感じになります。

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None


a = Analysis(['hello.py'],
             pathex=['HelloPyinstaller'],
             binaries=[],
             datas=[],
             hiddenimports=['pkg_resources.py2_warn'],
             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,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='hello',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True )

そして、このspecファイルを指定して再度ビルド。

> pyinstaller --onefile hello.spec

こうすることで、エラーにならなくなります。
もちろん、hiddenimportsに複数モジュール名を指定しなければいけないようなシチュエーションなら、「ビルド→確認→hiddenimportsに追加」の手順を、この後エラーが出なくなるまで繰り返すことになります。

え、指定が必要なモジュールを事前に把握する方法?
わかりませんw

--noconsoleオプションをつけてビルドしたファイルは何故かバーチャルマシン上でウイルス判定される

--noconsoleオプションをつけてビルドした.exeファイルは、バーチャルマシン上にコピーする前にウイルス判定になって検疫されていまうという謎仕様あり。

動作チェックの時は--noconsoleを入れないでビルドするようにしましょう。

※この問題の原因判明しました。続きは次の章追加して説明しています。

avastなどのウィルス対策ソフトからトロイの木馬として認識されてしまう問題→解決

ここまで書いた流れで作成した.exeファイルは、avastなどのウィルス対策ソフトがインストールされている環境に入れた途端、有害なファイルとして警告が出てしまい困りました。

検索してみたところ、多くの人がこの問題で困っているようですね。。
海外のavastの掲示板なんかでは、エンジニアからavastに対しての相談・皮肉・クレームなどで結構荒れています。

ちなみにこの問題の対策は、Pythonを64bitのものでインストールしなおすことで解決します。

面倒ですが、python 64bit版をダウンロード・インストールし、環境を構築しなおしましょう。
(先に32bitのpythonをアンインストールするのを忘れずに。両方共存とかややこしいことはやめましょう。)

公式ページから普通にdownloadをクリックするだけだと64bitのOSでも32bitのインストーラがダウンロードされてしまうため、Python Releases for Windowsから「Windows x86-64 executable installer」を選択してダウンロードします。

こうしてダウンロードしたインストーラでPythonをインストールしたら、また必要なパッケージをpipでインストールしなおしましょう。

> pip install -U pip setuptools
> pip install PyInstaller
> pip install pnadas numpy

(pandasやnumpyなどお好みで。)

もしこのとき「python38-32がどうのこうの」とエラーになるようなら環境変数がちゃんと64bitのパスに切り替わってないです。(僕の環境のことですハイ)

32ってのを消すなどして実際の64bitのPythonの位置にしましょう。

環境変数名の編集の開き方

  • 「ファイル名を指定して実行」ウィンドウを開く( [Win]+[R] )
  • 「control」と入力してEnter
  • 「システム」をクリック
  • 「システムの詳細設定」をクリック
  • 「システムのプロパティ」ウィンドウ→[詳細設定]タブ→[環境変数]をクリック
  • 〇〇のユーザー環境変数の「Path」を選択し[編集]をクリック

(もっと短縮したやりかたもありますが、これが一番覚えやすいんですよねー)

そして再度ビルド。

pyinstaller --onefile grouper.spec

specファイルは32bit版の環境でPyInstaller使った時のものがあれば、そのまま流用できるので、specファイルが既に作られているならそれを使用。

これで、avastなどの過敏なウイルス対策ソフトがインストールされている環境でも勘違いされることなく動くはずです!

まとめ

  • pythonの.pyを.exeにするにはPyInstallerライブラリを使う
  • --onefileと--noconsoleオプションがある
  • Windowsデベロッパーセンターの仮想マシンを使えば動作確認可能
  • エラーになるようなら.specファイルにモジュール名を追加
  • 32bitのPythonで作成した.exeは一部のウイルス対策ソフトからウイルスと勘違いされるから64bitのPythonをインストールしよう

こんな感じのお話でした。

-python

© 2022 ヂまるBlog