PACKAGING

作ったPythonアプリをEXEにして渡す

自分のPCでは動くのに、相手のPCにはPythonが入っていない。その1点を解決するのがPyInstallerです。最小手順から、データファイルの同梱・サイズ・Windowsのブロックまで、実際にビルドして確かめた結果をまとめました。

🎯 対象: tkinterアプリを人に渡したい初心者〜初級者 ⏱️ 読了: 約13分 🧪 実測: PyInstaller 6.22.3 / Windows 11

1. 結論:コマンド1行でEXEになる。問題はそのあと

PythonでGUIアプリを1本作ると、次に来るのは「これ、家族や同僚のPCでも動かしたい」という話です。相手にPythonをインストールしてもらう方法もありますが、その説明のほうがアプリ本体より長くなります。PyInstallerは、Python本体・使っているライブラリ・自分のコードをまとめて1つの実行ファイル(EXE)にするツールです。渡された側はダブルクリックするだけで済みます。

先に結論

pip install pyinstaller のあと pyinstaller --onefile --noconsole app.py と打てば、dist フォルダにEXEができます。ここまで数分。時間を取られるのは画像やCSVを同梱する設定起動の遅さWindowsにブロックされたときの対応の3つで、この記事の本題もそこです。

この記事の数値は、40行ほどのtkinterアプリを題材に、手元のWindows機で実際にビルドして計測したものです。

🧪
検証環境(2026-09-20 時点)

Windows 11 Pro(ビルド 26200)/Core i5-11400H/メモリ16GB/SSD/Python 3.12.10(64bit)/PyInstaller 6.22.3。macOSとLinuxでは計測していないため、この記事の数値はWindowsの話として読んでください。

2. 仕組みと、EXE化で解決しないこと

PyInstallerは「コンパイルして速くする」ツールではありません。スクリプトを解析して必要なモジュールを集め、Pythonインタプリタごと1か所に固め、起動用の小さなプログラム(ブートローダ)を先頭に付けています。中身はPythonのままです。ここから、期待してはいけない点が3つ出てきます。

よくある期待実際
軽くなるPython本体を同梱するので大きくなる。tkinterだけでも約9.9MB(実測)
速くなる処理速度は変わらない。むしろ起動は遅くなる(§5)
WindowsでLinux版も作れる作れない。公式は「クロスコンパイラではない。Windowsアプリを作るならWindowsで実行する」と明記している

中身がPythonのままということは、ソースコードは完全には隠れないという意味でもあります。バイトコードは専用ツールで取り出せるので、社外に出せない情報をコードに直接書いたまま配るのは避けてください。

対応するPythonは、PyPIの公開情報で >=3.8,<3.16(2026-09-20 確認・PyInstaller 6.22.3)。ライセンスはGPLv2以降ですが、作ったアプリを商用を含め自由に配布してよい例外条項が付いており、自分のアプリまでGPLになるわけではありません。ただしPyInstaller本体やブートローダを改変して配布する場合はGPLの条件に従います。同梱した他のライブラリの条件も別に付いてきます。

3. 最小手順(インストールから配布まで)

まだPythonを入れていない場合はPythonのインストール手順から。すでにアプリが1本動いているなら、作業は4ステップです。

pip install pyinstaller
pyinstaller --version

バージョンが表示されたら準備完了です。アプリのある場所へ移動してビルドします。

cd C:\Users\yourname\apps
pyinstaller --onefile memo_app.py

実行すると build(作業用)・dist(成果物)・memo_app.spec(設定ファイル)の3つができます。相手に渡すのは dist の中身だけです。

⚠️
最初は --noconsole を付けずにビルドする

--noconsole(別名 --windowed)は黒いコンソール窓を消すオプションですが、エラーメッセージの出口も一緒に消えます公式ドキュメントも「Pythonの詳細メッセージや警告は標準出力に出るため、--windowed では見えない」と書いています。まずコンソールありで動作確認し、問題が消えてから付け直すほうが結局は速いです。

最後の1ステップは「渡す前に自分で確かめる」こと。ビルドしたフォルダの外にEXEをコピーして起動します。元の場所ではソースや素材が隣にあるため、同梱し忘れていても動いてしまい、配布先で初めて気づくことになります。

4. onefile と onedir、どちらで配るか

PyInstallerの出力形式は2つです。既定は --onedir(フォルダ一式)で、--onefile を付けるとEXE1個になります。

比較項目--onefile(EXE1個)--onedir(フォルダ一式・既定)
渡しやすさファイル1個。チャットで送りやすいZIPで渡す。中身を消されると動かない
合計サイズ10,402,285バイト(約9.9MB)25,209,221バイト(約24.0MB・943ファイル)
起動(実測の中央値)0.76秒0.17秒
起動の仕組み毎回テンポラリへ展開してから動く展開なしでそのまま動く
向いている場面1人に1回渡す。USBで持ち運ぶ毎日使う。起動の速さが要る

onefileは見た目のわかりやすさと引き換えに、起動のたびに中身をテンポラリフォルダへ展開しています。実測ではこの展開に毎回0.6秒ほどを使い、onedirの約4.5倍の起動時間になりました。1日に何度も開くならonedir、1回渡して終わりならonefileで足ります。

展開先は実測で C:\Users\(ユーザー名)\AppData\Local\Temp\_MEI000005fc2 のような名前でした。通常終了なら消えますが、強制終了したときは展開先が残ったままになります(こちらも実測)。

5. 実測:サイズ・ビルド時間・起動時間

題材は、JSONからメモを読んで表示するだけの40行ほどのtkinterアプリです。そこへ重いライブラリを足すと何が跳ね上がるのかを、すべて --onefile で計りました。

同梱したものEXEのサイズビルド時間起動(初回/2回目以降)
tkinterのみ約9.9MB9.1秒2.22秒/0.73〜0.77秒
+ Pillow 12.2.0約28.2MB20.6秒未計測
+ matplotlib 3.11.1約109.9MB119.5秒9.21秒/3.67〜3.79秒

目を引くのはmatplotlibです。サイズは11倍、ビルド時間は13倍、起動は5倍になりました。グラフを1枚描くだけのアプリが110MBになり、起動に約3.7秒——「渡した相手が固まったと思ってもう一度ダブルクリックする」長さです。tkinterだけなら起動0.76秒なので、配っても違和感は出ません。

参考までに、同じスクリプトを python memo_app.py で直接動かすとウィンドウ表示は0.17〜0.19秒でした。onedirの0.17秒はこれと同水準で、遅さの原因はonefileの展開処理だと確認できます。

ℹ️
計測方法

起動時間は、プロセスの開始からウィンドウ描画までの差をアプリ側で記録し、5回測った値です。ビルド時間は build フォルダを消した状態からの所要時間で、消さずに再ビルドした2回目は1.0秒でした。直しながら何度も作り直す間は待たされません。

画像処理を含む例は画像ビューア(Pillow)、グラフを描く例は予算ダッシュボード(matplotlib)にあります。練習台にするなら最初のウィンドウのような小さいアプリが安全です。

6. つまずきやすい5か所

① 画像やCSVが「見つからない」と言われる

最も多いのがこれです。素材ファイルは自動では入らないので、--add-data で明示します。

pyinstaller --onefile --noconsole --add-data "data;data" memo_app.py

書式は「元の場所」と「EXEの中での置き場所」を区切り文字でつないだもの。上の例は、手元の data フォルダをEXEの中でも data という名前で置く指定です。

区切り文字はOSで違うと説明されがちですが、手元のPyInstaller 6.22.3ではWindowsでも ;: のどちらでもビルドが通り、どちらのEXEも起動しましたC:\... から書くフルパスでも同じ)。公式ドキュメントの表記は : です。迷うなら、Windowsは ;、macOS・Linuxは : と覚えておけば足ります。

同梱を忘れたままのEXEを起動すると、次のエラーで落ちます(コンソールありでビルドすると見える表示です)。

FileNotFoundError: [Errno 2] No such file or directory:
'C:\Users\(ユーザー名)\AppData\Local\Temp\_MEI000032902\data\messages.json'
[PYI-11524:ERROR] Failed to execute script 'memo_app' due to unhandled exception!

パスに _MEI が出てきたら、EXEの中を探しに行って見つからなかったという合図です。

② コードの中のパスを書き換える

同梱しただけでは足りません。open("data/messages.json") のような相対パスは、EXEでは起動時のカレントフォルダ基準になってしまいます。展開先を基準にするには次の関数を通します。

import os

def resource_path(rel_path):
    """開発中もEXEでも通用するパスを返す"""
    base = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(base, rel_path)

with open(resource_path("data/messages.json"), encoding="utf-8") as f:
    ...

EXE化の解説では sys._MEIPASS を使う書き方が有名ですが、現在の公式ドキュメントは「__file__ には常にフルパスが入るようになったので、こうしたリソースを探すには __file__ が推奨される」としています。実際に両モードのEXEで値を出力すると、次のようになりました。

ビルド形式__file__ のあるフォルダデータは見つかるか
onefileテンポラリの _MEI… フォルダ見つかる
onedirdist\アプリ名\_internal見つかる

sys._MEIPASS も同じ場所を指していたので、どちらでも動きます。新しく書くなら __file__ のほうがシンプルです。

③ 保存したはずの設定が消える

onefileのアプリが展開先フォルダに書き込むと、終了時に一緒に消えます。データを保存するToDoリストのようなアプリでは致命的です。保存先はEXE本体の場所を基準にします。

import os
import sys

# EXE本体のあるフォルダ(スクリプト実行時は .py のあるフォルダ)
app_dir = os.path.dirname(sys.executable if getattr(sys, "frozen", False)
                          else os.path.abspath(__file__))
save_path = os.path.join(app_dir, "todo.json")

sys.frozen はEXEとして動いているときだけ真になる目印で、公式ドキュメントに記載のある仕様です。「読むのは __file__ 基準、書くのは sys.executable 基準」と覚えてください。

④ アイコンを変える

アイコンは --icon app.ico で指定します。手元では256×256のICOでビルドが通り、サイズはほぼ変わりませんでした(なし9.92MB/あり9.86MB=ビルドごとの揺れの範囲)。エクスプローラーはアイコンのキャッシュで古いまま見えることがあるので、別名でコピーして確認します。

⑤ 動くはずのモジュールが「No module named」で落ちる

PyInstallerはコードを静的に解析するため、文字列から動的に読み込むモジュールは見落とされることがあります。そのときのエラーはこうなります。

ModuleNotFoundError: No module named 'statistics'
[PYI-13128:ERROR] Failed to execute script 'hidden_app' due to unhandled exception!

対処は --hidden-import モジュール名 を足すこと。公式の説明も「コード上に見えていないimportを指定する」です。ただし正直に書くと、標準ライブラリで再現を試みた今回の検証では依存解決の過程で同梱されてしまい、失敗を再現できませんでした(上のエラーは --exclude-module で意図的に外して起こしたものです)。外部ライブラリのプラグイン機構では実際に起きるので、メッセージの形だけ覚えてください。エラーの読み方はPythonエラーメッセージ一覧にあります。

7. ウイルス対策ソフトの誤検知とWindowsのブロック

EXE化で最後に残るのがこの問題です。理由は2つあり、「自分自身を展開して実行する」構造がマルウェアの手口と似ていること、そして多くの人が同じ既製のブートローダを使うため、そこが検出パターンとして登録されてしまうことです。後者は公式ドキュメントも、ブートローダを自分でビルドし直す理由として「広く使われている事前ビルド済みブートローダに起因する誤検知を避けるため」と説明しています。この再ビルドは当サイトでは未検証です。

⚠️
実測:16本中3本が起動をブロックされた

検証で作ったonefileのEXE16本を起動したところ、3本が起動時にブロックされました(「アプリケーション制御ポリシーによってこのファイルがブロックされました」=WinError 4551)。このPCはWindows 11のスマートアプリコントロールが有効で、Microsoft Defenderの脅威検出履歴は0件。ウイルスと判定されたのではなく、「署名も実績もない見知らぬ実行ファイル」としてブロックされた形です。同じソースで作り直した別のEXEはブロックされず、onedir版も起動しました。判定はファイル単位で、実行するまで分かりません。検証機1台・16本の観測で、相手の設定やファイル次第で変わる設計どおりの動作です。

つまり署名のない個人配布のEXEは、相手の環境によっては起動しません。自分のPCで動いたことは、相手のPCで動く保証になりません。この記事では検出を避ける小細工は扱いません。ウイルス対策ソフトの設定を変えさせる依頼は、相手のPCの防御を弱める行為で、業務用PCでは規定違反にもなり得ます。現実的な選択肢は3つです。

対処内容コスト
onedirで配るフォルダ一式で渡す。今回の検証ではブロックされなかったなし
誤検知を報告するウイルス検出時のみ、各社の誤検知窓口へ提出する。アプリケーション制御によるブロックは対象外手間と待ち時間
コード署名証明書発行元を証明する証明書をEXEに付ける。取得には審査と費用が要る年単位の費用

もう1つ、身も蓋もない選択肢があります。EXEにせず、ソースコード(.pyファイル)のまま渡すことです。相手にPythonを入れてもらう手間はありますが、中身が読めるので相手も安心で、署名のないEXEとしてのブロックも起きません(社内の実行ポリシーなど別の制限はあり得ます)。当サイトが公開しているサンプルアプリをすべて .py のソースで配り、EXEを配布していないのも同じ理由です。相手が数人なら、インストール手順を案内するほうが早いこともあります。

8. 配布前チェックリスト

渡す直前に、この6項目だけ確認してください。

確認すること理由
ビルドしたフォルダの外にコピーして起動した素材の同梱漏れは元の場所では発覚しない
保存機能が動き、再起動してもデータが残るonefileは展開先に書くと消える(§6-③)
パスワードやAPIキーを書いていないEXEからソースは取り出せる
ライブラリのライセンス表記を同梱した多くは著作権表示の保持を求めている
渡す相手のWindowsが同じ系統(64bit同士)作った環境向けのEXEしかできない
ブロックされたときの連絡先を伝えた§7の状況は相手側では原因がわからない

見落とされがちなのはライセンス表記です。自分のコードがMITでも、同梱したライブラリの条件は別に付いてきます。使っているものはPythonライブラリ一覧で確認できます。

なお、matplotlibを含むアプリのフルビルドは約2分かかり、その間CPUとディスクをかなり使います。設定を変えながら何度も作り直すなら、CPUとSSDに余裕のあるマシンほど待ち時間が短くなります(機種の目安はPython学習・開発におすすめのPCに。リンク先はアフィリエイト広告を含むページです)。

9. ほかの選択肢(Nuitka・cx_Freeze・auto-py-to-exe)

PyInstallerが合わないときの代替を3つ。版とライセンスはPyPIの公開情報で2026-09-20に確認した値です。

ツール版・ライセンス特徴向く人
Nuitka4.2.1/AGPLv3以降。生成物にはランタイム例外(LICENSE-RUNTIME.txt)があり自作アプリはAGPLにならないPythonをC経由で本当にコンパイルする。実行が速くなる場合がある速度を上げたい人。ライセンス条件を自分で確認できる人
cx_Freeze8.7.0/PSFライセンスPyInstallerと同系統の同梱ツール。対応Pythonは3.10以上3.16未満PyInstallerでうまくいかなかった人
auto-py-to-exe2.50.1/MITPyInstallerのオプションを画面で選べるGUIラッパー。中身はPyInstallerコマンドの記述が苦手な人

まずPyInstallerを試し、動かなければ他を検討すれば足ります。auto-py-to-exeは生成コマンドが画面に出るので学習にも使えます。3つとも当サイトでは未計測です。

10. よくある質問(FAQ)

Q. EXE化するとソースコードは見られなくなりますか?

完全には隠れません。EXEの中にはPythonのバイトコードが入っており、取り出して読む方法が知られています。パスワード・APIキー・社外秘の情報を直接書いたまま配布するのは避けてください。

Q. WindowsでmacOS用・Linux用のアプリは作れますか?

作れません。公式ドキュメントが「クロスコンパイラではない」と明記しており、各OSの実機か仮想マシンが要ります。

Q. EXEが100MBを超えました。小さくできますか?

本当に使っているライブラリだけになっているかを確認してください。手元の計測では、matplotlibを含むだけで約110MBになりました。仮想環境を作り、そのアプリに必要なものだけ入れてビルドするとムダが減ります。圧縮ツールのUPXを併用する方法もありますが、当サイトでは未計測です。

Q. 起動が遅いのですが、直せますか?

--onefile をやめて --onedir にすると改善する可能性があります。手元の同じアプリでは、起動が0.76秒から0.17秒になりました。onefileは起動のたびに中身をテンポラリへ展開するためです。

Q. セキュリティソフトやWindowsに止められたら、どう説明すればいいですか?

正直に「Pythonで作ったツールを実行ファイルにまとめたもので、署名がないため実績のないファイルとして扱われている」と伝えるのが筋です。設定変更のお願いは業務用PCでは規定違反になることがあるので、onedirで渡し直す・ソースのまま渡すといった代替を先に検討してください。

11. 次の一歩

EXE化が要るのは、アプリを作り終えた人だけです。まだ渡せるアプリがないなら、先に1本仕上げるほうが近道です。

つまずくのはたいてい素材の同梱か、渡した先でのブロックです。詰まったら§6と§7へ。

本記事は、生成AIを活用して下書きし、運営者が内容を確認・編集したうえで公開しています。AIの利用方針は免責事項をご覧ください。