LoginSignup
18
29

More than 3 years have passed since last update.

xlwingsを使ってPythonからExcel VBAマクロを呼び出す

Last updated at Posted at 2020-05-25

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とします)を追加し、以下を記述します。

VBA_example.xlsm!Module1
Sub hello_VBA()
    MsgBox "Hello VBA from Python."
End Sub

次に、同じフォルダーをVisual Studio Codeで開き、call_VBA.ipynbというファイルを作成します。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. マクロを実行

コードの説明すると、

  1. xlwingsをインポートします。as xwとしていますので、以下xwでxlwingsを呼び出します。
  2. 同じフォルダーにあるブックならxw.Bookで開けます。
  3. ブック内のマクロhello_VBAを変数macroに格納します。VBAユーザーだとここで??となるかもしれませんが、Pythonは関数も変数に格納することができます。
  4. macroを実行します。VBAだとCall macroとかになるかもしれませんが、Callは不要です。引数が必要なマクロの場合は、括弧内に引数を入れます。

となります。

これをVSCodeで実行中の画面は以下のとおりです:
ipynb実行画面.png

最後のセルを実行したとき止まりますが、Excelのウィンドウをアクティブにすると、次の画面が表示されます:
Hello VBA.png

ちゃんとVBAマクロが実行されてますね。

4. 応用(VBAマクロの連続実行)

UDFを使った実用的な例です。あるExcelファイルのボタンから、他のExcelファイルのVBAマクロを連続で実行できるようにしてみます。

適当なフォルダーでVBA_caller.xlsmを作成します。ワークシートの内容は次のようにします:
VBA_caller.png

  • シート名はVBA_caller
  • テーブルT.VBA_callerを作成(ホームタブのテーブルとして書式設定から。場所はどこでもOK。)
  • テーブルには列フォルダーファイルマクロを含むこと。

Alt+F11でVBAエディターの画面を開き、参照設定でxlwingにチェックを入れます。Module1を作成し、以下を貼り付けます:

VBA_caller.xlsm!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を作成します。

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.xlsmexample3.xlsmには標準モジュールに以下を保存しておきます:

example1.slsm~example3.xlsm!Module1
'プロシージャ‐名とMsgBoxの文字列は適宜変更
Sub example1()
    MsgBox "example1"
End Sub

これで準備ができました。

実行してみます。VBA_caller.xlsmAlt+F8を押し、mainを選択し、実行ボタンを押します。新しいExcelインスタンスでexample1.xlsmが開き、メッセージボックスにexample1と表示されれば成功です。OKボタンを押すと、example2.xlsm、example3.xlsmのマクロが次々実行されます。

5. さらに応用(VBAマクロの同時連続実行)

ここまでの内容なら、VBAでもほぼ同じことができます。しかし、VBA_caller.pyを少し書き換えると、複数マクロの同時実行もできるようになります。

修正その1: imoprt文の最後に以下を追加します。

VBA_caller.py
import joblib

修正その2: 以下のforループを削除し、

VBA_caller.py
    for _, row in df_target.iterrows():
        call_vba_single(row)

joblibを使った呼び出しに変更します。

VBA_caller.py
    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マクロの呼び出しについては書きませんでしたが、必要でしたらその旨コメントいただければと思います。

参考

18
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
29