概要

Pythonスクリプトを実行ファイル化することにより、Pythonがインストールされていない環境でもプログラムを実行できるようになる。
実行ファイル化のツールとして広く使用されており信頼性の高いPyInstallerやNuitkaが存在する。

PyInstallerの主な特徴を以下に示す。

  • 単一ファイルまたはフォルダ形式での出力
  • Windows、Linux、MacOSに対応
  • 依存モジュールの自動検出
  • データファイルやリソースの組み込み
  • GUIアプリケーションのサポート



複数ファイルで構成されるプロジェクトの構造

実際の開発では、単一ファイルではなく複数のモジュールに分割されたプロジェクト構造が一般的である。

想定するプロジェクト構造

この構造において、main.py ファイルがエントリーポイントとなり、他のモジュールをimportして使用する。

my_project/
├── main.py                 # エントリーポイント (メインスクリプト)
├── requirements.txt        # 依存パッケージ一覧
├── config.ini              # 設定ファイル (データファイル)
├── resources/              # リソースフォルダ
│   ├── images/
│   │   └── logo.png
│   └── data/
│       └── default_settings.json
├── src/                    # ソースコードのメインパッケージ
│   ├── __init__.py
│   ├── core/               # コア機能モジュール
│   │   ├── __init__.py
│   │   ├── engine.py
│   │   └── processor.py
│   ├── ui/                 # UI関連モジュール
│   │   ├── __init__.py
│   │   ├── main_window.py
│   │   └── dialogs.py
│   └── utils/              # ユーティリティモジュール
│       ├── __init__.py
│       ├── file_handler.py
│       └── logger.py
└── tests/                  # テストコード (実行ファイルには含めない)
    ├── __init__.py
    └── test_engine.py


エントリーポイント (main.py) の例

 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 """
 アプリケーションのエントリーポイント
 PyInstallerでexe化する際は、このファイルを指定する
 """
 
 import sys
 import os
 
 def get_base_path():
    """
    実行ファイルのベースパスを取得する。
    PyInstallerでパッケージ化された場合と、通常のPython実行の場合で
    パスの取得方法が異なるため、この関数で吸収する。
    """
    if getattr(sys, 'frozen', False):
       # PyInstallerで実行されている場合
       # sys._MEIPASS は一時展開フォルダのパス (--onefileの場合)
       # sys.executable は実行ファイル自体のパス
       return os.path.dirname(sys.executable)
    else:
       # 通常のPython実行の場合
       return os.path.dirname(os.path.abspath(__file__))
 
 def get_resource_path(relative_path):
    """
    リソースファイルへのパスを取得する。
    --add-data で含めたファイルにアクセスする際に使用する。
    """
    if getattr(sys, 'frozen', False):
       # PyInstallerの一時展開フォルダ内のパス
       base_path = sys._MEIPASS
    else:
       base_path = os.path.dirname(os.path.abspath(__file__))
    return os.path.join(base_path, relative_path)
 
 def main():
    """メイン関数"""
    # リソースファイルへのアクセス例
    config_path = get_resource_path('config.ini')
    logo_path = get_resource_path(os.path.join('resources', 'images', 'logo.png'))
 
    # 各モジュールのimport
    from src.core.engine import Engine
    from src.ui.main_window import MainWindow
    from src.utils.logger import setup_logging
 
    # ロギングの設定
    setup_logging()
 
    # アプリケーションの起動
    engine = Engine()
    window = MainWindow(engine)
    window.run()
 
 if __name__ == '__main__':
    main()



Windows

環境構築

コマンドプロンプトまたはPowerShellを開き、Pythonのバージョンを確認する。

python --version
pip --version


python コマンドが認識されない場合は、Pythonのインストール時に[Add Python to PATH]にチェックを入力し忘れている可能性がある。
その場合は環境変数を手動で設定、または、Pythonを再インストールする。

仮想環境の作成

プロジェクトフォルダに移動して仮想環境を作成する。
仮想環境を使用することで、実行ファイルに不要なパッケージが含まれることを防ぎ、ファイルサイズを最小限に抑えられる。

cd C:\path\to\my_project
python -m venv venv
venv\Scripts\activate


プロンプトの先頭に (venv) と表示されれば、仮想環境がアクティブになっている。

依存パッケージとPyInstallerのインストール

pip install pyinstaller
pip install -r requirements.txt


基本的なビルドコマンド

複数ファイルのプロジェクトでも、指定するのはエントリーポイントのファイルだけである。
PyInstallerは、そのファイルからimportされている全てのモジュールを自動的に追跡して、実行ファイルに含める。

pyinstaller main.py


このコマンドにより、main.py がimportしている src.core.enginesrc.ui.main_window 等のモジュールは自動的に検出されて、実行ファイルに組み込まれる。

生成されるディレクトリとファイル

ビルド時に生成されるディレクトリとファイル
ディレクトリ / ファイル 説明
build/ PyInstallerが作業中に使用する一時ファイルの格納場所。
配布時には不要。
dist/ 生成された実行ファイルと依存ファイルの格納場所。
your_script.spec PyInstallerの設定ファイル。
カスタマイズに使用する。


データファイルとリソースの追加

Pythonソースコード以外のファイル (設定ファイル、画像、JSONデータ等) は自動的には含まれない。
--add-data オプションで明示的に指定する必要がある。

pyinstaller --onefile ^
   --add-data "config.ini;." ^
   --add-data "resources;resources" ^
   main.py


--add-data オプションの書式は、"元のパス;実行ファイル内での配置先パス"となる。
Windowsではセミコロン (;) を区切り文字として使用する。

上記の例では、config.iniファイルは実行ファイルと同じ階層に配置され、resourcesフォルダはその中身ごとresourcesという名前で配置される。

GUIアプリケーション向けオプション

GUIアプリケーションの場合は、コンソールウィンドウを非表示にし、アイコンを設定する。

pyinstaller --onefile --noconsole ^
   --icon=resources\images\app_icon.ico ^
   --name=MyApplication ^
   --add-data "config.ini;." ^
   --add-data "resources;resources" ^
   main.py


主なオプション
オプション 説明
--onefile 単一の実行ファイルにまとめる
--noconsole コンソールウィンドウを非表示 (GUIアプリ向け)
--windowed --noconsoleと同じ
--icon アイコンファイルを指定 (.ico形式)
--name 出力ファイル名を指定
--add-data データファイルを追加
--hidden-import 自動検出されないモジュールを追加


specファイルを使用した詳細設定

複雑なプロジェクトでは、specファイルを使用して設定を管理することを推奨する。
まず、初回のビルドでspecファイルを生成する。

pyinstaller --onefile --noconsole --name=MyApplication main.py


生成されたMyApplication.specファイルを開いて、必要に応じて編集する。

 # -*- mode: python ; coding: utf-8 -*-
 
 block_cipher = None
 
 # データファイルの定義
 # 形式: (元のパス, 実行ファイル内での配置先)
 added_files = [
    ('config.ini', '.'),
    ('resources/images', 'resources/images'),
    ('resources/data', 'resources/data'),
 ]
 
 # 自動検出されないモジュールがある場合に追加
 hidden_imports = [
    # 例: 動的インポートを使用している場合
    # 'src.plugins.optional_module',
 ]
 
 # 除外するモジュール (ファイルサイズ削減のため)
 excludes = [
    'tkinter',        # GUIフレームワークを使わない場合
    'unittest',       # テストフレームワーク
    'pytest',
 ]
 
 a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[],
    datas=added_files,
    hiddenimports=hidden_imports,
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=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.datas,
    [],
    name='MyApplication',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,           # UPX圧縮を使用 (インストール済みの場合)
    upx_exclude=[],
    runtime_tmpdir=None,
    console=False,      # コンソールウィンドウを非表示
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon='resources\\images\\app_icon.ico',
 )


specファイルを編集した後は、以下に示すコマンドを実行してビルドする。

pyinstaller MyApplication.spec


Hidden Importsの対処

PyInstallerは静的解析によってimportを検出するが、以下のようなケースでは自動検出できないことがある。

  • 動的インポートを使用している場合
    importlib.import_module()
  • 文字列からモジュールを参照している場合
  • 一部の複雑なライブラリ
    pandas、scipy、scikit-learn、PIL等のサブモジュール


エラーメッセージに ModuleNotFoundError が含まれている場合は、そのモジュールを --hidden-import オプションで追加する。

pyinstaller --onefile --hidden-import=PIL._tkinter_finder main.py


または、specファイルの hiddenimports リストに追加する。

Windows用ビルドスクリプト

ビルド作業を効率化するため、バッチファイルを作成することを推奨する。

 @echo off
 setlocal
 
 REM 仮想環境のアクティベート
 call venv\Scripts\activate
 
 REM 出力ディレクトリの作成
 if not exist dist\windows mkdir dist\windows
 
 REM PyInstallerの実行
 pyinstaller --onefile --noconsole ^
    --distpath dist\windows ^
    --workpath build\windows ^
    --add-data "config.ini;." ^
    --add-data "resources;resources" ^
    --icon=resources\images\app_icon.ico ^
    --name=MyApplication ^
    main.py
 
 echo.
 echo ビルドが完了しました。出力先: dist\windows\MyApplication.exe
 pause



RHEL / SUSE

前提条件

PyInstallerはクロスコンパイルをサポートしていない。
Linux向けの実行ファイルを作成するには、Linux環境でPyInstallerを実行する必要がある。

もし、Windows環境からLinux向けの実行ファイルを作成する場合は、WSL2、Docker、仮想マシンを使用する。

環境構築

RHEL

まず、Pythonと開発ツールをインストールする。

# RHEL
sudo dnf install python3 python3-pip python3-devel
sudo dnf groupinstall "Development Tools"


RHELでは、デフォルトのPythonバージョンがシステムによって異なる。
例えば、Python 3.9以上を使用する場合は、以下に示すようにインストールする。

# Python 3.11を使用する場合
sudo dnf install python3.11 python3.11-pip python3.11-devel


SUSE

まず、Pythonと開発ツールをインストールする。
devel_basisパターンには、gcc、make等の基本的な開発ツールが含まれている。

# SUSE
sudo zypper install -t pattern devel_basis
sudo zypper install python3 python3-pip python3-devel


仮想環境の作成

プロジェクトディレクトリに移動して、仮想環境を作成する。

cd /path/to/my_project
python3 -m venv venv
source venv/bin/activate


特定バージョンのPythonを使用する場合は、そのバージョンを明示的に指定する。

python3.11 -m venv venv
source venv/bin/activate


依存パッケージとPyInstallerのインストール

pip install --upgrade pip
pip install pyinstaller
pip install -r requirements.txt


実行ファイルの作成

基本的なコマンド構文はWindowsと同様である、--add-data オプションの区切り文字がコロン (:) になることに注意する。

pyinstaller --onefile \
   --add-data "config.ini:." \
   --add-data "resources:resources" \
   main.py


GUIアプリケーションの場合は、X11環境が必要となる。

pyinstaller --onefile --noconsole \
   --add-data "config.ini:." \
   --add-data "resources:resources" \
   --name=MyApplication \
   main.py


specファイルを使用した設定

Linuxでのspecファイルは、パス区切り文字がスラッシュになる点を除き、Windowsとほぼ同じ構造である。

 # -*- mode: python ; coding: utf-8 -*-
 
 block_cipher = None
 
 # データファイルの定義 (Linux版)
 # パス区切り文字は / を使用
 added_files = [
    ('config.ini', '.'),
    ('resources/images', 'resources/images'),
    ('resources/data', 'resources/data'),
 ]
 
 hidden_imports = []
 
 excludes = [
    'tkinter',
    'unittest',
    'pytest',
 ]
 
 a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[],
    datas=added_files,
    hiddenimports=hidden_imports,
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=excludes,
    noarchive=False,
 )
 
 pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
 
 exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='MyApplication',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,        # CLIアプリの場合はTrue、GUIの場合はFalse
 )


実行権限の付加と動作確認

# 実行権限の付加
ls -la dist/MyApplication
chmod u+x dist/MyApplication    # 必要な場合

# 動作確認
./dist/MyApplication


GLIBCバージョンの互換性について

PyInstallerで生成されたバイナリは、ビルド環境のGLIBCバージョンに依存する。
新しいGLIBCでビルドしたバイナリは、古いGLIBCを持つシステムでは動作しない可能性がある。

配布先の環境で確実に動作させるためには、以下に示す事柄を考慮する。

  • ターゲット環境と同じ、あるいは、それより古いバージョンのディストリビューションんでビルドする。
    例 : RHEL 9向けの実行ファイルを作成する場合は、RHEL 9環境でビルドする。


現在のGLIBCバージョンは以下に示すコマンドで確認できる。

ldd --version
# または
rpm -q glibc


共有ライブラリの依存関係の確認

生成されたバイナリが依存している共有ライブラリを確認するには、ldd コマンドを使用する。

ldd dist/MyApplication


"not found: と表示されるライブラリがある場合、そのバイナリはターゲット環境で動作しない可能性がある。

Linux向けビルドスクリプト

 #!/usr/bin/env sh
 
 # 仮想環境のアクティベート
 source venv/bin/activate
 
 # 出力ディレクトリの作成
 mkdir -p dist/linux
 
 # PyInstallerの実行
 pyinstaller --onefile \
    --distpath dist/linux \
    --workpath build/linux \
    --add-data "config.ini:." \
    --add-data "resources:resources" \
    --name=MyApplication \
    main.py
 
 echo ""
 echo "ビルドが完了しました。出力先: dist/linux/MyApplication"


スクリプトに実行権限を付与する。

chmod u+x build_scripts/build_linux.sh



Nuitkaを使用した高度なコンパイル

Nuitkaは、PythonコードをC言語にコンパイルしてから実行ファイルを生成するツールである。
PyInstallerと比較して、実行速度が向上し、リバースエンジニアリングへの耐性も高くなる。

Nuitkaのインストール

pip install nuitka


Windows環境では、Visual Studio Build Tools または MinGW-w64 が必要である。
Linux環境では、GCCと関連ツールが必要である。

# RHEL
sudo dnf install gcc gcc-c++

# SUSE
sudo zypper install gcc gcc-c++


基本的な使用方法

# CLIアプリケーションの場合
python -m nuitka --standalone --onefile your_script.py

# GUIアプリケーションの場合
python -m nuitka --standalone --onefile --windows-disable-console --windows-icon-from-ico=icon.ico your_script.py


Nuitkaは初回ビルド時に時間が掛かるが、生成される実行ファイルは一般的にPyInstallerよりも高速に動作する。


トラブルシューティング

ModuleNotFoundErrorが発生する

  • 原因
    PyInstallerが動的インポートや特定のライブラリのサブモジュールを検出できていない。
  • 対処法
    エラーメッセージに表示されているモジュールを --hidden-import オプションで追加する。


pyinstaller --onefile --hidden-import=missing_module main.py


よく問題になるライブラリと対処例を以下に示す。

Hidden Importが必要になることが多いライブラリ
ライブラリ 追加が必要なモジュールの例
pandas pandas._libs.tslibs.timedeltas
PIL / Pillow PIL._tkinter_finder
scikit-learn --collect-submodules=sklearn


ファイルが見つからないエラー

  • 原因
    データファイル (設定ファイル、画像等) が実行ファイルに含まれていない、または、パスの解決が正しくない。
  • 対処法
    --add-data オプションでファイルを正しく含めているか確認する。
    ソースコード内で get_resource_path() メソッドを使用してパスを解決しているかどうかを確認する。


実行ファイルのサイズが大きすぎる

対処法を以下に示す。

  • 仮想環境を使用して、必要なパッケージのみをインストールした環境でビルドする。
  • specファイルの excludes リストに、使用していないモジュールを追加する。
  • UPX圧縮を使用する。
    もしUPXがインストールされていれば、PyInstallerは自動的に使用する。

    UPXのインストール手順を以下に示す。
     # RHEL
     sudo dnf install upx
     
     # SUSE
     sudo zypper install upx
    


"GLIBC_X.XX not found" エラー

  • 原因
    ビルド環境のGLIBCのバージョンが、実行環境より新しい。
  • 対処法
    ターゲット環境と同じか、それより古いバージョンのLinuxでビルドする。
    Dockerを使用して古い環境でビルドする方法も効果的である。

    Dockerを使用したビルド方法を以下に示す。
     # 例: openSUSE Leap 15.1のDockerコンテナでビルド
     docker run -it -v $(pwd):/app opensuse/leap:15.1 bash
     
     # コンテナ内でPythonとPyInstallerをセットアップしてビルド
     # ...略
    



手法の選択指針

ツール選択の指針
用途 推奨ツール 備考
通常の用途で手軽に実行ファイルを作成したい PyInstaller 豊富な情報があり、トラブルシューティングも容易
実行速度を重視する Nuitka ビルド環境の準備とビルド時間が増加する
コードの難読化を強化したい Nuitka C言語へのコンパイルにより難読化される
クロスプラットフォーム対応が必要 PyInstaller 各OSの環境でそれぞれビルドを行う必要がある


クロスプラットフォーム対応が必要な場合は、各OSの環境 (実機、VM、Docker、WSL2等) でそれぞれビルドを行う必要がある。
CI/CDパイプライン (GitHub Actions等) を活用すると、複数プラットフォーム向けのビルドを自動化できる。


外部リンク