xlwingsのUDFの実用的な使い方として、PythonからExcel VBAマクロを呼び出す方法を説明します。「xlwingsとは何ぞや?」という方には、PythonのインストールからxlwingsのUDFの基本的な使い方までを以下で投稿していますので、そちらをご覧ください。
VBAユーザーのためのPython入門 ~xlwingsでExcelからPythonを呼び出す~
1. 背景
脱Excel VBAを目指してPythonで社内EUCツールを作り始めたとしても、社内には先人たちが作ったExcel VBAツールが山のようにあり、それらも使用しなければならないということがあると思います。VBAが嫌いだからと言って捨てる訳にも行かず、いっそのことPythonで書き直そうかと思っても忙しいし量が多くてそれもできず。結局はVBAからは逃れられないのかと頭を抱えます。
そして、一連の作業で複数のExcel VBAツールを使う場合は、たいてい都度ファイルを開きマクロを実行して、終わったら次のファイルという流れになるかと。じゃあ、自動的にVBAマクロを次々と実行するプログラムをVBAで作ればいいじゃんとなるかもしれません。
いや、やっぱり止めましょうVBA。それ、Pythonでできますから。
本記事では、PythonからExcel VBAマクロを実行する方法を説明します。この方法なら、既存のVBA資産を活かしつつ、Pythonで快適にコーディングできるようになります。
2. 環境
以下の環境で試しています。
- OS: Windows 10 version 1909
- Python: version 3.7.5
- xlwings: version 0.18.0
- エディター: VSCode version 1.45.1 & Python拡張機能 version 2020.5.80290
3. 基本
公式ドキュメント(Python API)を参考に、PythonからExcel VBAマクロを呼び出してみましょう。
適当なフォルダーで、VBA_example.xlsm
というファイルを作成し、Alt+F11
でVBAエディターを開きます。適当なモジュール(とりあえずModule1とします)を追加し、以下を記述します。
Sub hello_VBA()
MsgBox "Hello VBA from Python."
End Sub
次に、同じフォルダーをVisual Studio Codeで開き、call_VBA.ipynb
というファイルを作成します。call_VBA.ipynb
で以下を(できれば一行ずつ)実行します。
import xlwings as xw # 1. xlwingsをインポート
wb = xw.Book('VBA_example.xlsm') # 2. ブックを開く
macro = wb.macro('hello_VBA') # 3. マクロを取得
macro() # 4. マクロを実行
コードの説明すると、
- xlwingsをインポートします。
as xw
としていますので、以下xw
でxlwingsを呼び出します。 - 同じフォルダーにあるブックなら
xw.Book
で開けます。 - ブック内のマクロ
hello_VBA
を変数macro
に格納します。VBAユーザーだとここで??となるかもしれませんが、Pythonは関数も変数に格納することができます。 -
macro
を実行します。VBAだとCall macro
とかになるかもしれませんが、Call
は不要です。引数が必要なマクロの場合は、括弧内に引数を入れます。
となります。
最後のセルを実行したとき止まりますが、Excelのウィンドウをアクティブにすると、次の画面が表示されます:
ちゃんとVBAマクロが実行されてますね。
4. 応用(VBAマクロの連続実行)
UDFを使った実用的な例です。あるExcelファイルのボタンから、他のExcelファイルのVBAマクロを連続で実行できるようにしてみます。
適当なフォルダーでVBA_caller.xlsm
を作成します。ワークシートの内容は次のようにします:
- シート名は
VBA_caller
- テーブル
T.VBA_caller
を作成(ホームタブのテーブルとして書式設定から。場所はどこでもOK。) - テーブルには列
フォルダー
、ファイル
、マクロ
を含むこと。
Alt+F11
でVBAエディターの画面を開き、参照設定でxlwingにチェックを入れます。Module1を作成し、以下を貼り付けます:
Sub main()
Dim rg_target As Range
Set rg_target = Worksheets("VBA_caller").ListObjects("T.VBA_caller").Range
Application.DisplayAlerts = False
var_log = xlwings_udfs.call_vba(rg_target)
Application.DisplayAlerts = True
End Sub
Pythonのスクリプトを準備します。同じフォルダーに以下の内容のファイルVBA_caller.py
を作成します。
import os
from pathlib import Path
import numpy as np
import pandas as pd
import xlwings as xw
os.chdir(Path(__file__).parent.absolute().resolve())
@xw.sub
@xw.arg('df_target', pd.DataFrame, index=False, header=True)
def call_vba(df_target: pd.DataFrame):
def call_vba_single(row):
#設定読み込み
wb_path, wb_name, macro_name = row['フォルダー'], row['ファイル'], row['マクロ']
#マクロ実行用Excelインスタンス生成し、ブックを開く
app = xw.apps.add()
xl_path = (Path() / wb_path / wb_name).resolve()
wb = app.books.open(str(xl_path))
#マクロ実行
app.display_alerts = False #マクロ実行中に、Excelから「別のプログラムでOLEの操作が完了するまで待機します。」というメッセージが表示され、実行が止まるため
wb.macro(macro_name)()
app.display_alerts = False #呼び出し先でTrueに戻されることがあるので、再びFalseに設定
#ブックを保存し、マクロ実行用ExcelインスタンスをKill
wb.save()
wb.close()
app.kill()
for _, row in df_target.iterrows():
call_vba_single(row)
@xw.sub
def kill_excel():
os.system('taskkill /f /im excel.exe')
#デバッグ用
if __name__ == '__main__':
xw.serve()
VBA_caller.py
作成後、VBA_caller.xlsm
に戻り、xlwingsタブのImport Functions
ボタンを押し、VBA_caller.py
の内容をUDFとして取り込みます。これで、呼び出し元の準備は終了です。
実験用に、呼び出し先のExcelファイルも準備してみましょう。
┌example1
│ └example1.xlsm
├example2
│ ├example2.xlsm
│ └example3
│ └example3.xlsm
├VBA_caller.py
└VBA_caller.xlsm
example1.xlsm
~example3.xlsm
には標準モジュールに以下を保存しておきます:
'プロシージャ‐名とMsgBoxの文字列は適宜変更
Sub example1()
MsgBox "example1"
End Sub
これで準備ができました。
実行してみます。VBA_caller.xlsm
でAlt+F8
を押し、main
を選択し、実行ボタンを押します。新しいExcelインスタンスでexample1.xlsm
が開き、メッセージボックスにexample1
と表示されれば成功です。OKボタンを押すと、example2.xlsm、example3.xlsmのマクロが次々実行されます。
5. さらに応用(VBAマクロの同時連続実行)
ここまでの内容なら、VBAでもほぼ同じことができます。しかし、VBA_caller.py
を少し書き換えると、複数マクロの同時実行もできるようになります。
修正その1: imoprt文の最後に以下を追加します。
import joblib
修正その2: 以下のforループを削除し、
for _, row in df_target.iterrows():
call_vba_single(row)
joblibを使った呼び出しに変更します。
joblib.Parallel(n_jobs=-1, verbose=10)([
joblib.delayed(call_vba_single)(row) for _, row in df_target.iterrows()
])
修正(?)その3: (2までで上手く行かなかったら試してください)xlwingsタブのInterpreter
にpython.exeのパスを入力します。Anacondaをデフォルト設定でインストールしていれば、C:\ProgramData\Anaconda3\python.exe
になります。
VBA_caller.xlsm
に戻り、再度Alt+F8
からmain
を実行してみましょう。今度は複数のExcelが同時に立ち上がり、マクロが同時実行されます。これはVBAには(たぶん)できないことであり、複数のマクロを同時実行することで、全体の処理時間を短くすることができます。
joblibはPythonで簡単に並列処理をできるようにするためのライブラリーで、詳しい説明はこちらなどをご覧ください。
おわりに
最後まで読んでいただき、ありがとうございます。
今回は基本的な使い方ということで、引数が必要になるようなVBAマクロの呼び出しについては書きませんでしたが、必要でしたらその旨コメントいただければと思います。