1. はじめに
ExcelVBAやAccessVBAとPythonを連携させたツールを作る際、設計の初期段階で必ずぶつかる分岐点があります。
「Python側を .py のまま置くのか、.exe に固めて配布するのか」
この選択は、単なる好みの問題ではありません。配布先の端末にPythonをインストールできるのか、仕様変更の頻度はどれくらいか、利用者は何人でどんなリテラシーなのか——こうした運用条件によって、正解が真逆になります。そして厄介なことに、この判断を誤ると「作った後で全部作り直し」という手戻りが発生します。
本記事では、この2つの形式について、呼び出し方のコード例・メリット・デメリット・向いているケースを整理します。どちらが優れているかではなく、「どういう条件なら、どちらを選ぶべきか」を判断できる状態をゴールにします。
2. この記事の対象者
- VBAでツールを作っているが、VBAだけでは限界を感じ始めている方
- VBAからPythonを呼び出す構成を検討している、または既に実装している方
- Pythonで書いたスクリプトを、Pythonを知らない同僚・他部署に配布したい方
- 情報システム部門の統制が厳しく、端末にPythonをインストールできない環境の方
- PyInstallerでexe化したが、起動が遅い・ウイルス対策ソフトに引っかかる等で悩んでいる方
- EUCツールの保守性・引き継ぎやすさを重視している方
前提知識としては、VBAの基本文法と、Pythonの基本的な実行方法(python script.py)が分かっていれば十分です。
3. 前提:VBA×Pythonの基本構成と、2つの形式
3-1. 基本アーキテクチャ
VBAとPythonを連携させる場合、筆者は以下の役割分担を推奨しています。
| レイヤー | 担当 | 役割 |
|---|---|---|
| フロントエンド | VBA | UI、入力値チェック、シート操作、実行指示、結果の整形表示 |
| バックエンド | Python | 重い処理、外部API通信、PDF/画像処理、機械学習、複雑なデータ加工 |
| 連携部 | ファイル(CSV/JSON) | VBA→Pythonの入力、Python→VBAの出力受け渡し |
VBAとPythonはメモリを共有できないため、中間ファイルを介したハンドシェイクが最も堅実です。
[VBA] 入力データをCSV/JSONで書き出す
↓
[VBA] Pythonを起動(.py or .exe)
↓
[Python] 入力ファイルを読む → 処理 → 出力ファイルを書く → 完了フラグを立てる
↓
[VBA] 完了を検知 → 出力ファイルを読み込んでシートに反映
この構成自体は、.py でも .exe でも変わりません。変わるのは「Pythonをどう起動するか」と「配布時に何が必要か」だけです。
3-2. 2つの形式
| 形式 | 実体 | 実行端末に必要なもの |
|---|---|---|
.py 形式 |
Pythonのソースコードそのもの | Python本体 + 依存ライブラリ |
.exe 形式 |
PyInstaller等でパッケージ化した実行ファイル | 不要(exe単体で動く) |
言い換えると、Pythonの実行環境を「端末側に持たせる」か「成果物側に同梱する」かの違いです。
4. 各形式の詳細
4-1. .py 形式でVBAから呼び出す
概要
Pythonのソースファイルを、Pythonインタプリタ経由で直接実行します。開発中の状態がそのまま本番の状態になる、最もシンプルな形式です。
VBA側の実装例
最も重要なポイントは、python.exe をフルパスで指定することです。環境変数PATHに依存すると、端末ごとのPythonバージョン差異で不具合が発生します。仮想環境(venv)を使う場合も同様に、venv内の python.exe を直接叩きます。
Option Explicit
'==============================================================
' Pythonスクリプト(.py)を同期実行する
' 戻り値: Pythonプロセスの終了コード(0 = 正常終了)
'==============================================================
Public Function RunPythonScript(ByVal argCsvPath As String) As Long
Dim wsh As Object
Dim pyExe As String
Dim pyScript As String
Dim cmd As String
Dim exitCode As Long
' venv内のインタプリタをフルパス指定(PATH依存を排除)
pyExe = "C:\euc_tools\venv\Scripts\python.exe"
pyScript = ThisWorkbook.Path & "\src\main.py"
' 存在チェック(ここを省略すると原因不明のエラーになりがち)
If Dir(pyExe) = "" Then
Err.Raise vbObjectError + 513, , "Python実行環境が見つかりません: " & pyExe
End If
If Dir(pyScript) = "" Then
Err.Raise vbObjectError + 514, , "スクリプトが見つかりません: " & pyScript
End If
' パスに空白が含まれる場合に備え、各要素をダブルクォートで囲む
cmd = """" & pyExe & """ """ & pyScript & """ """ & argCsvPath & """"
Set wsh = CreateObject("WScript.Shell")
' 第2引数: 0 = ウィンドウ非表示 / 第3引数: True = 終了まで待機(同期)
exitCode = wsh.Run(cmd, 0, True)
Set wsh = Nothing
RunPythonScript = exitCode
End Function
Shell 関数ではなく WScript.Shell.Run を使う理由は、同期実行(完了待ち)と終了コードの取得が1行でできるためです。VBA組み込みの Shell は非同期のため、別途ポーリング処理が必要になります。
Python側の実装例
import sys
from pathlib import Path
def main() -> int:
try:
input_csv = Path(sys.argv[1])
# --- 処理本体 ---
df = read_and_process(input_csv)
df.to_csv(input_csv.with_name("output.csv"), index=False, encoding="utf-8-sig")
return 0
except Exception as e:
# ログファイルに残す(標準出力は文字コード事故が起きやすい)
Path("error.log").write_text(str(e), encoding="utf-8")
return 1
if __name__ == "__main__":
sys.exit(main())
sys.exit() で終了コードを返すことで、VBA側の exitCode で成否を判定できます。
メリット
| 項目 | 内容 |
|---|---|
| 修正の即時反映 | ファイルを上書き保存するだけで次回実行から反映される。ビルド作業が不要 |
| デバッグが容易 | VSCode等でブレークポイントを張ってステップ実行できる。エラー時のトレースバックもそのまま読める |
| ファイルサイズが小さい | ソースコードのみなので数KB〜数百KB。共有フォルダやGitで扱いやすい |
| バージョン管理と相性が良い | テキストファイルなのでGitの差分が読める。レビューが成立する |
| ライブラリの更新が柔軟 |
pip install -U で個別に更新できる。セキュリティパッチの適用が速い |
| xlwingsとの親和性 |
RunPython によるVBA⇔Python双方向連携は .py 前提 |
デメリット
| 項目 | 内容 |
|---|---|
| 配布先全端末に環境構築が必要 | Python本体 + 依存ライブラリのインストールが前提。10人に配れば10台分の作業が発生 |
| 環境差異による事故 | Pythonのバージョン、ライブラリのバージョン差で「自分の端末では動くのに」が起きる |
| インストール申請のハードル | 統制の厳しい企業では、そもそもPythonをインストールできないことが多い |
| ソースコードが丸見え | 業務ロジックやAPIキーの取り扱いに注意が必要。利用者による意図しない改変リスクもある |
| 利用者のリテラシー依存 | 「pipって何ですか」という利用者に環境構築を依頼するのは現実的ではない |
4-2. .exe 形式でVBAから呼び出す
概要
PyInstaller等のツールで、Pythonインタプリタ・依存ライブラリ・自作コードをまとめて1つの実行ファイルにパッケージ化します。配布先にPythonがなくても動作します。
ビルド手順
# 仮想環境を有効化した状態で
pip install pyinstaller
# パターンA: 単一ファイル(配布は楽だが起動が遅い)
pyinstaller --onefile --noconsole --name euc_tool src/main.py
# パターンB: フォルダ形式(起動が速く、誤検知も相対的に少ない)
pyinstaller --onedir --noconsole --name euc_tool src/main.py
主なオプションの意味は次のとおりです。
| オプション | 効果 |
|---|---|
--onefile |
単一のexeにまとめる。実行時に一時フォルダへ展開するため起動が遅い |
--onedir |
exe + 依存ファイル群のフォルダ構成。展開処理がないため起動が速い |
--noconsole |
黒いコンソールウィンドウを表示しない(VBAから裏で呼ぶ場合に必須級) |
--name |
出力するexeの名前を指定 |
--add-data |
設定ファイルやテンプレートを同梱する |
実務では --onedir を推奨します。 起動速度が体感で明確に速く、ウイルス対策ソフトの誤検知率も下がる傾向があるためです。配布時はフォルダごとZIPで渡すか、共有フォルダに配置します。
exe化する際のPython側の注意点
exe化すると、__file__ が期待どおりに動きません。実行ファイル自身の場所を取得するには sys.frozen で分岐します。
import sys
from pathlib import Path
def get_base_dir() -> Path:
"""スクリプト実行時とexe実行時の両方で、正しい基準ディレクトリを返す"""
if getattr(sys, "frozen", False):
# PyInstallerでexe化されている場合
return Path(sys.executable).parent
else:
# 通常の .py 実行の場合
return Path(__file__).resolve().parent
BASE_DIR = get_base_dir()
CONFIG_PATH = BASE_DIR / "config.json"
この分岐を入れておくと、開発中は .py として、配布時は .exe として、同じコードのまま両対応できます。
VBA側の実装例
呼び出し方は .py 形式とほぼ同じで、インタプリタの指定が不要になる分、シンプルになります。
'==============================================================
' exe形式のPythonツールを同期実行する
'==============================================================
Public Function RunPythonExe(ByVal argCsvPath As String) As Long
Dim wsh As Object
Dim exePath As String
Dim cmd As String
exePath = ThisWorkbook.Path & "\bin\euc_tool\euc_tool.exe"
If Dir(exePath) = "" Then
Err.Raise vbObjectError + 515, , "実行ファイルが見つかりません: " & exePath
End If
cmd = """" & exePath & """ """ & argCsvPath & """"
Set wsh = CreateObject("WScript.Shell")
RunPythonExe = wsh.Run(cmd, 0, True)
Set wsh = Nothing
End Function
なお、起動をバッチファイル経由にしておくと、実行パスやログ出力先の変更をVBAの再コンパイルなしに調整できるため、保守性の面で有利です。
@echo off
rem run_tool.bat
cd /d "%~dp0"
".\bin\euc_tool\euc_tool.exe" %1
exit /b %errorlevel%
メリット
| 項目 | 内容 |
|---|---|
| 配布先にPython不要 | exeとExcelファイルを渡すだけで動く。環境構築ゼロ |
| 実行環境が固定される | ビルド時点のPython・ライブラリバージョンが同梱されるため、端末差異による事故が原理的に起きない |
| 統制環境で通しやすい | 「インストール作業なし・ファイル配置のみ」という形にできるため、情シスの承認を得やすいケースがある |
| ソースコードが直接は見えない | ロジックの秘匿性が上がる(※後述のとおり完全ではない) |
| 利用者のリテラシーを問わない | ダブルクリックで動くのと同じ感覚で使える |
| 監査時の説明がしやすい | 「このexeが処理の実体」と特定でき、バージョンを固定して保管できる |
デメリット
| 項目 | 内容 |
|---|---|
| 修正のたびに再ビルドと再配布 | 1文字直すだけでもビルド→配布のサイクルが回る。緊急修正に弱い |
| ファイルサイズが大きい | pandas等を含めると数十MB〜数百MBになる。メール添付は現実的でない |
| 起動が遅い | 特に --onefile は一時展開が入るため、起動に数秒かかることがある |
| ウイルス対策ソフトの誤検知 | PyInstaller製exeがマルウェアと誤判定されることがある。事前に情シスへホワイトリスト登録の相談が必要 |
| デバッグしづらい | エラー時にトレースバックが表示されない(--noconsole の場合)。ログ出力の設計が必須 |
| 完全な秘匿ではない | pyinstxtractor等でバイトコードの抽出が可能。APIキーのハードコードは避けるべき |
| ビルド環境の維持コスト | ビルドできる端末・手順を保守し続ける必要がある。属人化しやすいポイント |
5. 【表まとめ】.py 形式と .exe 形式の比較
| 比較項目 |
.py 形式 |
.exe 形式 |
|---|---|---|
| 実行端末に必要なもの | Python本体 + 依存ライブラリ | 不要 |
| 配布物 | ソースファイル(数KB〜) | 実行ファイル(数十MB〜数百MB) |
| 環境構築の手間 | 全端末で必要 | 不要 |
| 環境差異による不具合 | 起こりうる | ほぼ起きない |
| 修正の反映 | 上書き保存で即時 | 再ビルド + 再配布が必要 |
| 起動速度 | 速い | やや遅い(特に --onefile) |
| デバッグのしやすさ | 容易(IDEでステップ実行可) | 困難(ログ設計が必須) |
| ソースコードの秘匿性 | なし(可読) | あり(ただし完全ではない) |
| バージョン管理 | Gitで差分管理しやすい | バイナリのため差分管理に不向き |
| ウイルス対策ソフトの誤検知 | ほぼなし | 起こりうる |
| ライブラリ更新の柔軟性 | 高い(pipで個別更新) | 低い(再ビルドが必要) |
| 利用者に求めるリテラシー | 中〜高 | 低 |
| 情シス承認のハードル | 高い(インストール申請) | 中(ファイル配置 + 誤検知対策) |
| xlwings連携 | 可能 | 不可(別方式が必要) |
| ビルド作業 | 不要 | 必要 |
| VBA側の実装難易度 | やや高い(パス指定が煩雑) | 低い(exeを叩くだけ) |
6. どちらを選ぶべきか:ユーザー・ケース別の判断
6-1. .py 形式が最適なケース
① 開発者本人、または少人数の技術チームで使う場合
利用者が自分でPython環境を用意でき、エラーメッセージを読める場合、exe化のコストは純粋な無駄になります。修正の即時性というメリットだけを享受できます。
② 仕様変更が頻繁に発生する開発初期・PoC段階
「試して直して試して」を高速で回す局面では、ビルド待ち時間が積み重なると致命的です。まず .py で作り込み、仕様が固まってからexe化する、という二段構えが現実的です。
③ 社内標準でPythonが配布されている環境
Anaconda等が標準ソフトとして全端末に展開済みの組織なら、環境構築のデメリットはそもそも存在しません。この場合は素直に .py を選びます。
④ データ分析・調査系のアドホックなツール
一度きり、あるいは自分だけが使う分析処理では、パッケージ化するメリットがありません。
⑤ xlwingsで双方向連携をしたい場合
xlwingsの RunPython はPython環境を前提とするため、この構成を採る時点で .py 形式が確定します。
6-2. .exe 形式が最適なケース
① Pythonを知らない現業部門へ配布する場合
「Pythonをインストールしてください」という一文が説明書に入った瞬間、そのツールの利用率は激減します。exe形式なら、利用者から見れば「Excelのボタンを押すと動く」だけであり、Pythonの存在を意識させずに済みます。
② 端末へのソフトウェアインストールが禁止されている統制環境
金融機関をはじめとする厳格な環境では、Pythonのインストール申請が通らない、あるいは通っても数ヶ月かかることが珍しくありません。exe形式は、この制約を回避できる有力な選択肢になります(ただし、実行ファイルの配置自体にルールがある場合も多いため、事前に情シスへの確認は必須です)。
③ 数十人〜全社規模で展開する場合
配布先が10台を超えたあたりから、環境構築の手間と環境差異のトラブル対応コストが加速度的に膨らみます。「全端末で同じバイナリが動く」という保証には、それだけの価値があります。
④ 仕様が安定しており、更新頻度が低いツール
月次処理や年次処理のように、一度作れば数年変わらないツールでは、再ビルドのデメリットがほとんど顕在化しません。
⑤ ロジックの改変を防ぎたい場合
利用者が善意で(あるいは悪意なく)ソースコードを書き換えてしまうと、処理結果の正当性が担保できなくなります。監査対応が必要な業務では、実行体を固定できることが重要です。
⑥ 業務ノウハウの秘匿性が求められる場合
計算ロジックそのものが競争力の源泉であるケースでは、ソースの可読性が低いほうが望ましいことがあります。ただし前述のとおり完全な保護ではないため、認証情報のハードコードだけは絶対に避けてください。
7. 【表まとめ】ケース別の推奨形式
| ケース/条件 | 推奨形式 | 主な理由 |
|---|---|---|
| 開発者本人・少人数の技術チームで利用 | .py |
環境構築が問題にならず、修正の即時性が活きる |
| 開発初期・PoC・仕様変更が頻繁 | .py |
ビルド待ちがイテレーション速度を殺す |
| 社内標準でPython配布済み | .py |
環境構築のデメリットが存在しない |
| アドホックな分析・調査ツール | .py |
パッケージ化のコストに見合わない |
| xlwingsで双方向連携したい | .py |
仕組み上Python環境が必須 |
| 非エンジニアの現業部門へ配布 | .exe |
環境構築ゼロで利用開始できる |
| ソフトウェアのインストールが禁止された環境 | .exe |
Python本体のインストールが不要 |
| 配布先が10台以上/全社展開 | .exe |
環境差異のトラブル対応コストを回避できる |
| 仕様が安定した月次・年次処理 | .exe |
再ビルドのデメリットが顕在化しない |
| 監査対応・処理の同一性担保が必要 | .exe |
実行体をバージョン固定して保管できる |
| ロジックの改変を防ぎたい | .exe |
利用者がソースを直接編集できない |
| ノウハウの秘匿性が求められる | .exe |
可読性が下がる(※完全な保護ではない) |
| 緊急のバグ修正が頻発しうる運用 | .py |
再ビルド・再配布の遅延を避けられる |
| ネットワーク帯域・容量に制約がある | .py |
配布サイズが圧倒的に小さい |
| ウイルス対策ソフトの例外登録が困難 | .py |
誤検知リスクを避けられる |
8. おわりに
.py 形式と .exe 形式の選択は、「開発効率」と「配布容易性」のトレードオフに集約されます。
-
.py形式は、開発者にとって快適で、修正が速く、デバッグしやすい。ただし配布先ごとの環境構築という重い前提条件がつく -
.exe形式は、利用者にとって快適で、環境差異による事故がない。ただしビルドの手間とサイズ・起動速度・誤検知という代償を払う
実務での現実的な進め方としては、「開発中は .py、配布時に .exe」という二段構えが最もバランスが取れています。前述の sys.frozen による分岐を最初から仕込んでおけば、同一のコードベースで両対応でき、後から形式を切り替える際の手戻りを最小化できます。
なお、第3の選択肢として、Embeddable Package(Python公式の埋め込み用配布版)を共有フォルダに配置し、そこから .py を実行するという方式もあります。端末へのインストールを伴わずにPython環境を用意できるため、統制の厳しい環境で .py 形式のメリットを活かしたい場合の折衷案になります。こちらについては、機会があれば別記事で扱いたいと思います。
ツールは「作って終わり」ではなく、「配って、使われて、保守されて」初めて価値を生みます。ファイル形式の選択という一見地味な判断が、その後の運用コストを大きく左右します。本記事が、その判断の一助になれば幸いです。
最後までお読みいただき、ありがとうございました。
参考リンク
PyInstaller(exe化)
-
PyInstaller Manual(公式マニュアル)
- Pythonアプリケーションとその依存関係を1つのパッケージにまとめるツールの公式ドキュメント。パッケージ化されたアプリはPythonインタプリタやモジュールをインストールせずに実行できます。まずはここから。
-
Using PyInstaller(使い方・venvとの組み合わせ)
- 複数のPythonバージョン/ライブラリの組み合わせを扱う場合にvenvを使う方法が解説されています。本記事の「ビルド環境の維持」に関わる部分です。
-
pyinstaller コマンドラインオプション一覧
-
--onefile/--onedir/--noconsoleなどの正確な挙動を確認できます。onefileモード時の展開先を指定するオプションなども記載されています。
-
-
Run-time Information(
sys.frozen/sys._MEIPASS)- 本記事の
get_base_dir()の根拠となるページです。バンドルされたアプリの起動時にブートローダーがsys.frozen属性を設定し、バンドルフォルダの絶対パスをsys._MEIPASSに格納します(onedir構成ではバンドル内の_internalフォルダ、onefile構成では一時フォルダを指します)。
- 本記事の
-
What PyInstaller Does and How It Does It(動作原理)
- スクリプトを解析して必要なモジュールとライブラリを検出し、Pythonインタプリタごと1つのフォルダまたは実行ファイルにまとめる仕組みが説明されています。exeのサイズが大きくなる理由の理解に役立ちます。
-
How to Install PyInstaller
-
pyinstallerコマンドが見つからない場合の対処(PATHの確認方法)も記載されています。
-
Python実行環境の配布
-
4. Using Python on Windows — Python公式ドキュメント
- 「おわりに」で触れたEmbeddable Packageの解説はこちら。展開後の埋め込み用配布物は、環境変数・レジストリ設定・インストール済みパッケージを含めてユーザーのシステムからほぼ完全に隔離されます。ただし、通常のPythonインストールと同じようにpipで依存関係を管理することはサポートされていない点に注意が必要です。
-
Python Releases for Windows(ダウンロードページ)
- Embeddable Package(
python-3.x.x-embed-amd64.zip)はこちらから入手できます。
- Embeddable Package(
VBAからの外部プロセス起動
-
Shell function(Microsoft Learn / VBA言語リファレンス)
- VBA組み込みの
Shell関数。実行に成功するとプログラムのタスクIDをVariant(Double)で返し、起動できなかった場合はエラーが発生します。
- VBA組み込みの
-
VBA-Docs: shell-function.md(GitHub版・詳細な注記あり)
- 本記事で
ShellではなくWScript.Shell.Runを推奨した理由がここに書かれています。既定ではShell関数は他のプログラムを非同期に実行するため、後続のステートメントが実行される前に起動したプログラムが終了しているとは限りません。
- 本記事で
-
Run Method(Windows Script Host / Microsoft Learn)
-
WshShell.Runの公式リファレンス。新しいWindowsプロセスでプログラムを起動し、実行完了までスクリプトを待機させることで同期的な実行が可能になります。第2引数(ウィンドウスタイル)と第3引数(待機フラグ)の詳細も確認できます。
-
-
WshShell Object(Microsoft Learn)
-
Run/Exec/Environmentなど、WScript.Shellオブジェクト全体の仕様。標準出力を取得したい場合はExecメソッドの利用も検討できます。
-
xlwings(.py 形式での双方向連携)
-
xlwings Documentation(トップ)
- VBAマクロをPythonコードで置き換えたり、Pythonでユーザー定義関数(UDF、Windowsのみ)を書いたりでき、NumPy配列やPandasのSeries/DataFrameも完全にサポートされています。
-
RunPython(VBAからPythonを呼び出す)
-
RunPythonVBA関数を使うにはxlwingsアドイン(またはVBAモジュール)が必要である点など、導入時の前提条件が整理されています。
-
-
Add-in & Settings(インタプリタパス等の設定)
-
Interpreter設定でPythonインタプリタのパスを指定でき、仮想環境やconda環境にも対応しています。本記事で述べた「インタプリタをフルパス指定する」という考え方はここでも同様です。
-
-
Quickstart
-
xlwings quickstartによるプロジェクト雛形の作成手順。まず動かしてみたい方はこちらから。
-