Python
Excel
VBA
xlwings
Excel関数

ExcelにPythonが搭載?その後 - xlwings を使おう

マイクロソフトが Excel に Python を搭載することを検討しているというニュースが流れたのは1年前のことで、結構話題になりました。昨年の Python Advent Calendar 2017 では、ExcelにPythonが搭載されることを期待して「ExcelにPythonが搭載?」という記事を書きましたが、今回は、その続編を書きます。

そのニュースというのは、マイクロソフトが Excel に Python を搭載するかどうかを検討するためアンケートを実施したということなのですが、詳しく知りたい方は、Publickeyの「ExcelにPython搭載、マイクロソフトが検討。アンケートを実施中」という記事がわかりやすいので、そちらをみてください。

その後どうなったかというと、マイクロソフトが運営しているコミュニティサイト「Excel’s Suggestion Box」に投稿されている「Python as an Excel scripting language」という要望のページに結果が公表されるはずなのですが、3月1日にマイクロソフトのExcelの責任者からアンケートに対するお礼の言葉がありました。しかしながら、1年を経過してもアンケートの詳しい結果やどう対応するかについての具体的な回答はなく放置されたままです。それで少し詳しく調べてみると、redditの方に非公式の回答がありました。

11月6日にreditの 'ask me anything' にマイクロソフトの Excel チームが登場して、「いつExcelにPythonが搭載されるのか?」という質問に対して以下のように回答しています。

Cool to see the excitement around Python.
We had an awesome response to our survey on the Python UserVoice item. We’re working on the best way to address that feedback.

  • In the meantime, these are some great tools you can use like PyXLL and XLWings
  • Additionally, you can also host your backend logic via a web service in any language, including Python, and you can use web add-ins or a custom function to call that web service.

要するに、マイクロソフトが Excel に搭載するのは JavaScript であって、Python を使いたいのであれば PyXLLxlwings を使えということのようです。

これを見たときには、すごく残念に思って、Qiitaの記事に「今後は Linux でしか使わなくなると思うので」と書きましたが、よく考えてみるとこれが正解ではないかと思うようになってきました。

Excel チームから名前を挙げられた PyXLL と xlwings ですが、PyXLLの方は優れた製品なのですが、商用で年 $330 の使用料が必要になってくるので、個人や一般的な会社で手軽に使えるものではないと思います。それで、xlwings を使ってみました。使ってみると xlwings 便利なのがわかりました。

Excel で Python を使いたいのならば、xlwings を使いましょう。

xlwings に関する日本語の記事はあまりないようなので、まず、xlwings の紹介をしておきたいと思います。以下は主として Windows を対象にします。

Python から Excel を操作する

xlwings を使うと Python から Excel を操作することができます。自分の場合は、Jupyter Notebook から使っています。

インストール及び openpyxl との違い

Python から Excel を操作する場合は、pipxlwingsをインストールします。依存パッケージとして pywin32 と comtypes が必要ですが、xlwingsをインストールすれば同時に自動的にインストールされます。また、Anaconda の場合は標準でxlwings等利用に必要なものはインストールされています。インストールの方法をもう少し詳しく説明すると次のようになります。

# python.orgのバイナリーをPATHを設定してインストールした場合及び仮想環境を起動している場合
pip install xlwings
# python.orgのバイナリーをPATHを設定せずにインストールした場合
py -m pip install xlwings
# anaconda の場合は、xlwings はインストール済、アップデートしたい場合
conda update xlwings
# anacondaで最新のxlwingsを使いたい場合
conda install -c conda-forge xlwings

python には、xlwings 以外にも次のような Excel を操作できるライブラリーが存在するので、それらもインストールしておきましょう。Anaconda の場合はそれらも標準でインストールされます。

  • openpyxl Excelファイル(.xlsx)の読み書きが可能
  • xlrd Excelファイル(.xls, .xlsx)のデータを読むことが可能
  • xlwd Excelファイル(.xls)にデータとフォーマットを書き込むことが可能
  • xlswriter Excelファイル(.xlsx)にデータとフォーマットを書き込むことが可能

openpyxl 等こられのライブラリーは、Excelファイルを操作するものです。一方、xlwings は、ファイルではなくて Excel そのものを操作するライブラリーです。それで、xlwings を使うためには、同じマシン内に Excel がインストールされている必要があります。そのため、xlwings は Linux では使うことができません。xlwings の処理は遅いので、Python で Excel の帳票を作りたい場合には openpyxl を使った方がいいです。

Jupyter Notebook から使う

サンプルデータとして、気象庁の過去の気象データ・ダウンロードのページからダウンロードした札幌の気象データを用意しました。

data2012-2014.csv
data2015-2017.csv

Pandas を使うとそのようなデータを便利に扱うことができます。

import numpy as np
import pandas as pd
import xlwings as xw

df = pd.read_csv('https://creativeweb.jp/img/data/data2015-2017.csv', encoding='cp932')
df

xw01.png

こういう場合に、Excelでデータ全体を見えるように表示したいと思うことはないでしょうか。そういう時に簡単に使えるのが xlwings です。Excel を自分で立ち上げて「空白のブック」を選択するか、Jupyter Notebook で以下のスクリプトを実行すると「空白のブック」が立ち上がります。

wb = xw.Book()   

そして、以下のスクリプトを実行してみてください。これで、「空白のブック」にデータが表示されます。Excelのシートの左上だけ指定すればいいので簡単です。

xw.Range('A1').value = df

なお、xw.Rangeは、その時点でアクティブなシートなので、入力中の Excel のブックがある場合に「空白のブック」を立ち上げずに実行すると、入力中の Excel のブックにデータが入力されてしまって「あれれ」となるので注意してください。

その処理時間を以下のスクリプトで計測してみると 480 ms 程度なので実用上の問題はないと思います。

%time xw.Range('A1').value = df

次に、Jupyter Notebook で使用しているデータを Excel で編集したいというケースもあると思います。これをサンプルを使って説明すると、まず、サンプルデータだとヘッダー行が複雑なので、以下のスクリプトを実行してヘッダー行を一行にして、不要な列を削除しておきます。

usecols = [0, 1, 2, 4, 5, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20, 22, 23, 25, 26, 27]
df = pd.read_csv('https://creativeweb.jp/img/data/data2015-2017.csv', encoding='cp932', header=None, index_col=0, skiprows=6, usecols=usecols)
df.columns = ["平均気温", "平均気温_品質情報", "最高気温", "最高気温_品質情報",
    "最低気温", "最低気温_品質情報", "降水量", "降水量_現象なし情報", "降水量_品質情報",
    "最深積雪", "最深積雪_現象なし情報", "最深積雪_品質情報", "降雪量", "降雪量_現象なし情報", "降雪量_品質情報",
    "平均風速", "平均風速_品質情報", "日照時間", "日照時間_現象なし情報", "日照時間_品質情報"]
df.index.names = ["年月日"]

そして、上で説明したように以下のスクリプトで、Excel のシートにデータが表示されます。

xw.Range('A1').value = df

そのデータを編集後、Jupyter Notebook からは、左上のセルを指定した以下のスクリプトで取得できます。簡単でしょう。こちらの処理時間は 95 ms 程度なので書き込みより速いです。

df1 = xw.Range('A1').options(pd.DataFrame, expand='table').value

このように簡単に扱えるのは、xlwings に Converters と Options の機能があるためです。上の例で言うとoptionspd.DataFrameを指定することで、Range を Pandas の DataFrame に変換してくれます。xlwings の Converters に対応しているのは、何も指定しなければ Python の list になります。それ以外に、dictionary、Pandas の DataFrame と Series、Numpy の array に対応しています。また、expand='table' とすることで、左上のセルを指定するだけでテーブルを取得することができます。

この機能は、下で紹介する UDF(ユーザー定義関数)のケースでも非常に便利な機能になります。詳しくは公式ドキュメント「Converters and Options」のページをみてください。

xlwings を使えば、Excel の API や マクロを利用することも可能です(参照 公式ドキュメント Python API)。しかし、それらをどんどん使用すると処理が遅くなります。VSTO(Visual Studio Tools for Office)が失敗したのと同じことになります。プログラム言語としては、VBA よりも Python の方がずっと優秀なので、できるだけ Python 側で処理をして、Excel の CSV エディターとしての機能等 python では不足している機能だけを有効に活用すればいいのではないかと思います。もし、プログラムで Excel の帳票を作成したいのであれば openpyxl を使った方が処理がずっと速いです。

Excel から Python を利用する

「Excel に Python が搭載」という題名からすれば、こちらの方がメインの使い方になるかと思います。VBA が20年ぐらい放置されたままになっている旧式のプログラム言語なので、Excel から Python を利用したいというケースは非常に多いと思います。xlwings を使うと Excel からはマクロと UDF(ユーザー定義関数)で Python を利用できるようになります。なお、UDF が使えるのは Windows 版だけで Mac 版には対応していません。

インストール

Excel から Python で作成したマクロや UDF を使いたい場合は、次の作業が必要になります。

注意:Anaconda で試したところ、デフォルトでインストールするとPATHを通さずにpython3.7 がインストールされますが、その状態では、xlwings がうまく動作しません。当分の間は、インストールの時に次のように「勧められない」と表示されますが「Add Anaconda to my PATH environment variable」にチェックをしてインストールするか、conda install python=3.6 として python3.6 を使うようにした方がいいと思われます。
xw02.png

最初に使う時に必要な作業

Excel でマクロを使っている人は、既に知っていると思いますが、excel でマクロを動作させるためには、セキュリティセンターのマクロの設定を変更して以下のようにしておきます。
xw10.png

Excelの[開発]メニューは標準では隠されていますが、xlwings を Excel 側で使用する場合は[開発]メニューをしばしば利用するので表示されるように変更して置いた方がいいです。[開発]のリボンからは、[マクロのセキュリティ]ボタンをクリックすれば上で説明した[セキュリティセンター]の[マクロの設定]ダイアログが表示されます。

マクロの設定を上記のようにしても '#NAME?' エラーが発生する場合は、[セキュリティセンター]の信頼できる場所にプロジェクトのディレクトリーを追加するようにしてください。

また、Python 用のエディター兼デバッガーとして、PyCharm Community か Visual Studio Code をインストールしておくと便利です。どちらも無料で使えます。

最初に使う時とアップデートした時に必要な作業

xlwings の Excel アドイン (add-in)が必要になります。アドインのインストールは、以下のコマンドでおこないます。なお、Python に PATH を通していない場合は、Pythonのシステムディレクトリの Scripts ディレクトリに xlwings.exe がインストールされているのでフルパスで指定してください。

xlwings addin install

これを手作業でする場合は、GitHub の Releases Page に行って、インストールしているのと同じバージョンの 'xlwings.xlam'をダウンロードします。Excel のメニューから[開発]->[Excel アドイン]を選択すると[アドイン ダイアログ]が表示されるので、そこで[参照]ボタンをクリックして、ダウンロードした'xlwings.xlam'ファイルを選択して[OK]ボタンをクリックします。

xlwings アドインのインストールが終了すると、Excel のメニューに以下の図のように「xlwings」が追加されます。それを選択すると以下のようなリボンが表示されます。

xw03.png

パネルの左上に「Interpreter」の項目がありますが、PATH が通っている python を使用する場合は、空白のままで構いません。python に PATH を通していない場合や仮想環境を使いたい場合には、「Interpreter」の項目に、フルパスで実行ファイル pythonw.exe を入力するようにします。例えば、python.org のバイナリーをデフォルトでインストールしている場合であれば、C:\Users\user\AppData\Local\Programs\Python\Python37\pythonw.exe というように入力します。なお、'user', 'Python37' 等は適当なものに修正してください。

xlwings が不要になったときは、以下のコマンドで xlwings アドインを削除できます。

xlwings addin remove

クイックスタート

プログラミング言語を使い始める時の慣例に従って、'Hello World'を表示する UDF を作成してみます。適当なディレクトリで以下のコマンドを実行してみてください。

xlwings quickstart hello

'helio' というディレクトリが作成され、その中に 'hello.xlsm' と 'hello.py' というファイルができます。'hello.py' には、既に以下のようなスクリプトが記述されています。上側の 'def hello_xlwings()' がマクロ用のスクリプトで、下側の 'def hello(name)' が UDF 用のスクリプトです。

import xlwings as xw

def hello_xlwings():
    wb = xw.Book.caller()
    wb.sheets[0].range("A1").value = "Hello xlwings!"

@xw.func
def hello(name):
    return "hello {0}".format(name)

既に 'hello' という名前の UDF ができているので、'hello.xlsm' を起動します。

'_xwings.conf' という名前のシートが表示されますが、'xwings.conf' という名前に変更すればワークブック単位で Interpreter 等の設定を変更できます。'xwings.conf' という名前のファイルを用意すればディレクトリー単位でもInterpreter 等の設定の変更をおこなうことができます。設定をリボンでするのか、それとも個別にするのかは好みで選択すればいいと思います。

Interpreter の設定が終わったら、メニューで [xlwings] を選択してください。下のようにリボンが表示されるので、赤の四角で囲んだ [Import Functions] ボタンをクリックしてください。
xw04.png

新しい UDF を作成した場合には [Import Functions] ボタンをクリックすることで、 UDF の情報が、VBA の標準モジュールに登録されて、excel のワークシートで利用できるようになります。

シート[Sheet1]に移動して、以下の図のように =h まで入力するとオートコンプリート機能が働いて 'hello' が表示されるようになっています。

xw05.png

=hello(A1) と入力して、hello World! と表示されれば成功です。

既存のExcel ファイルで利用したい場合

既存のExcel ファイルで、xlwings で作成した UDF を利用したい場合には、まずマクロ機能を有効にするため '.xlsm' として保存する必要があります。次に、メニューから[開発]->[Visual Basic]で、Visual Basic for Application のエディターを立ち上げます。そこのメニューから[ツール]->[参照設定]を選択すると以下のようなダイアログが表示されるので、xlwings にチェックをして参照設定をします。
xw06.png

それから、Python のスクリプトを同じ名前で作成するか、そうでなければ[xlwings]のリボンの[UDF Modules] の項目で名前を指定するかします。名前を指定する場合には拡張子'.py'は省略します。また、複数の Python のファイルを使用する場合は'common_udfs;myproject'のように';'で区切るようにします。

実用的な UDF とマクロを作ってみる

前置きがものすごく長くなってしまいましたが、一番重要なのはこの部分です。xlwings では 'dynamic array formula' が既に使えるのすごく便利な UDF が作れます。また、マクロも作成可能です。

文字列を区切り文字で分割する SPLIT 関数を作ってみる

Excel のワークシート関数にないもので、あったら便利だと思うものの一つが、文字列を指定した区切り文字で分割してくれるものです。例えば、'hello world' を空白で分割して 'hello' と 'world'に分割してくれるもので、Google Sheets では、'SPLIT'というワークシート関数があります。

Excel にはなぜないのかというと、現時点ではワークシート関数は一つのセルにだけ値を返すものなので、複数のセルに値を返すことはできないためです。でも、xlwings であればできます。

Python の str.split() を使って、SPLIT 関数を作ってみます。

@xw.func
@xw.ret(expand='table')
def xwsplit(text, delimiter=None, maxsplit=-1):
    return text.split(delimiter, maxsplit)

そうすると、以下の図のように、A1セルに文字列を入力して、B1セルに =xwsplit(A1, " ") と入力するとそれ以降の右側の複数のセルに文字列を分割したものを出力してくれます。スクリプトの方で @xw.ret(expand='table') としている所がポイントで、図をみればわかるように配列数式を使って実現していて、'dynamic array formula' と呼ばれています。
xw07.png
区切り文字を一つだけではなくて複数使いたい場合がありますが、その場合は Python の re.split() を使うと簡単に実現できます。

@xw.func
@xw.ret(expand='table')
def xwsplit(text, delimiter=None, use_regex=False, remove_empty_text=False, maxsplit=-1):
    if use_regex:
        result = re.split(delimiter, text, maxsplit=maxsplit + 1)
    else:
        result = text.split(delimiter, maxsplit)
    if remove_empty_text:
        return list(filter(lambda x: x != '', result))
    else:
        return result

下の図は、括弧'()'で囲まれているものを分割した例です。正規表現を使うので "\(|\)" のようにエスケープが必要になったりして区切り文字の表現が複雑になるのですが、自分で使うのであればいいのではないでしょうか。それよりも、UDF が簡単に作成できるメリットが大きいと思います。
xw08.png

仕上げとして、以下のように docstring を書いておきましょう。

@xw.func
@xw.ret(expand='table')
@xw.arg('text', doc='分割するテキスト')
@xw.arg('delimiter', doc='区切り文字')
@xw.arg('use_regex', doc='正規表現を使うかどうかを指定。デフォルトは False')
@xw.arg('remove_empty_text', doc='空のテキストを削除するかどうかを指定。デフォルトは False')
@xw.arg('maxsplit', doc='最大の分割数を指定。デフォルトは -1で分割の回数に制限はなし')
def xwsplit(text, delimiter=None, use_regex=False, remove_empty_text=False, maxsplit=-1):
    """text を delimiter で指定した区切り文字で分割する"""
    if use_regex:
        result = re.split(delimiter, text, maxsplit=maxsplit + 1)
    else:
        result = text.split(delimiter, maxsplit)
    if remove_empty_text:
        return list(filter(lambda x: x != '', result))
    else:
        return result

そうすると「関数の挿入」ダイアログが以下のように説明付きで表示できるようになります。
xw09.png

これ以外にも、Python の正規表現操作 're.findall(pattern, string, flags=0)', 're.sub(pattern, repl, string, count=0, flags=0)' 等を使って便利な UDF が作れると思います。Excel の VBA でも、VBScript を使えば正規表現が使えていましたが、VBScript は役割を終えた言語なので Python に移行しておくのがいいのではないでしょうか。

データを外部から取ってくる

Excel の「データの取得と変換」の機能もデータの種類数が少ない時には便利なのですが、必要なテーブルの数が多くなってくると手作業が増えてコピペ等を延々しないといけなくなるので、プログラムで処理をして効率化をしたい場合も多いと思います。Python には、RDB、NoSQL データベースから始まって Web スクレイピングまで、あらゆる種類のデータを取得するための強力で豊富なライブラリーが揃っています。

上の「Jupyter Notebook から使う」で使った札幌の気象データを Excel のシートに取り込む Python のスクリプトを マクロと UDF で書いてみると、以下のようなコードになります。

import xlwings as xw
import pandas as pd
from functools import lru_cache

URL = 'https://creativeweb.jp/img/data/'

@lru_cache(maxsize = 16)
def get_file(filename):
    usecols = [0, 1, 2, 4, 5, 7, 8, 10, 11, 12, 14, 15, 16, 18, 19, 20, 22, 23, 25, 26, 27]
    df = pd.read_csv(URL + filename + '.csv', encoding='cp932', header=None, parse_dates=True, index_col=0, skiprows=6,
                     usecols=usecols)
    df.columns = ["平均気温", "平均気温_品質情報", "最高気温", "最高気温_品質情報",
                  "最低気温", "最低気温_品質情報", "降水量", "降水量_現象なし情報", "降水量_品質情報",
                  "最深積雪", "最深積雪_現象なし情報", "最深積雪_品質情報", "降雪量", "降雪量_現象なし情報", "降雪量_品質情報",
                  "平均風速", "平均風速_品質情報", "日照時間", "日照時間_現象なし情報", "日照時間_品質情報"]
    df.index.names = ["年月日"]
    return df

@xw.sub
def data_2015_2017():
    wb = xw.Book.caller()
    wb.sheets.active.range('A1').value = get_file('data2015-2017')

@xw.func
@xw.ret(expand='table')
def xw_data_by_file(filename):
    return get_file(filename)

@lru_cache(maxsize = 16)というデコレーターは、データをメモリーにキャッシュしています。Web からのデータなので処理の高速化と Web の負荷の軽減を図るためです。xlwings の UDF は COMサーバー経由で Excel と連携しているので、UDF を使用しているワークブックが立ち上がっている間は、[xlwings]リボンの [Restart UDF Server]ボタンをクリックするまで、キャッシュは効きます。

このようにデータをテーブルで返す必要がある場合、Excel では UDF は使えませんがが、xlwings だと Dynamic Arrays が使えるのでマクロでも UDF でもできます。UDF を使った方がパラメータを指定しやすいのどちらかというと扱いやすいケースが多いように感じていますが、Dynamic Arrays が使えるとマクロと UDF に殆ど違いがなくなるのでケースバイケースで状況に合わせて操作しやすい方を使用すればいいと思います。

以下のコードは、札幌の気象データを指定した期間分だけ表示する UDF です。上のコードへの追加になります。

from dateutil.parser import parser
from datetime import timedelta

def get_data(start_, end_):
    p = parser()
    if type(start_) is str:
        start_ = p.parse(start_)
    if type(end_) is str:
        end_ = p.parse(end_)

    year = ((start_.year - 2000) // 3) * 3 + 2000
    df = pd.DataFrame()
    while year <= (end_ - timedelta(days = 1)).year:
        df = df.append(get_file(f'data{year}-{year + 2}'))
        year += 3
    f = (df.index >= start_) & (df.index < end_)
    return df[f]

@xw.func
@xw.ret(expand='table')
def xw_data_by_perid(start_, end_):
    return get_data(start_, end_)

このコードだと次の図のように使うこともできます。年月日が数字になっていますが書式を日付に設定すれば大丈夫です。
xw12.png
現時点では配列式は操作が面倒なのですが、Excel 本体に Dynamic Array と FILTER, SORT 等の Dynamic Array を使ったワークシート関数が導入されれば、今の操作の面倒さは解消されると思われます。

ここまで来れば、データサイエンスの世界で鍛えられた Pandas 等の Python のライブラリーを使用して比較的容易にプログラムが可能です。マイクロソフトが放置している VBA よりも遙かに強力です。

例えば、指定した期間の毎月の平均気温は以下のようなコードで計算できます。

@xw.func
@xw.ret(expand='table')
def xw_data_monthly_mean(column, start_, end_):
    df = get_data(start_, end_)
    f = df[column + '_品質情報'] > 4
    return (df.loc[f, column].resample('M').mean() + 0.00001).round(1)

結果は、以下の図のようになります。
xw13.png

請求書を作成するマクロを作る

Excel の VBA の初心者用のマニュアルによくあるのが、請求書を作成するマクロです。これを xlwings を使って作ってみます。

まず、請求データと請求書の様式を用意する必要があります。請求データについては、以下のように適当に作成してみました。
xw14.png
請求書の様式の方は、下の図のように「ネ申Excel」を使いました。「ネ申Excel」については多くの批判がありますが、「表印刷ソフト」として使う時は「ネ申Excel」を使うしかないと思います。また、「ネ申Excel」にデータを直接コピーせずに、印刷設定の範囲外にコピーして、そこから「ネ申Excel」にワークシート関数でリンクしています。
xw15.png
詳しくは、データをダウンロードできるようにしていますので、そちらをみてください。

次に、Excelの'.xlsm' ファイルと python マクロを記述する '.py' ファイルをセットで作成します。ここでは、'test.xlsm' と 'test.py' ということにしておきます。

'test.xlsm' には、下の図のように入力時に設定したい請求日等の情報を入力しておきます。
xw16.png
それと、請求書のPDFを作成したい時には、PDF を作成するマクロを標準モジュールに次のように作成しておきます。

test.xlsm
Sub CreatePdf(wb As Workbook, path_pdf As String)
    wb.ExportAsFixedFormat Type:=xlTypePDF, filename:= _
        path_pdf, Quality:=xlQualityStandard, _
        IncludeDocProperties:=True, IgnorePrintAreas:=False, OpenAfterPublish:= _
        False
End Sub

以下が Python のスクリプトです。作成した Excel のマクロCreatePdfを呼ぶ時には引数でWorkBookオブジェクトが必要になりますが、'xlwings'の'Book'の'api'プロパティで取得できます。また、最後の4行はデバッグ用のコードです。

test.py
import xlwings as xw
import pandas as pd

# 請求書の様式と請求データを保存しているディレクトリをフルパスで記述
PATH = r"C:\Users\user\Documents\sample"

@xw.sub
def 請求書作成():
    wb = xw.Book.caller()
    請求情報 = wb.sheets['請求書作成'].range('A1').options(expand='table').value
    create_pdf = wb.macro('CreatePdf')

    wb_請求データ = xw.Book(PATH + "\\請求データ.xlsx")
    df_請求データ = wb_請求データ.sheets['Sheet1'].range('A1')\
        .options(pd.DataFrame, expand='table', index=False).value

    顧客リスト = df_請求データ["顧客"].unique()
    for 顧客 in 顧客リスト:
        df_顧客請求データ = df_請求データ[df_請求データ["顧客"] == 顧客]
        wb_請求書様式 = xw.Book(PATH + r"\請求書様式.xlsx")
        sht_請求書様式 = wb_請求書様式.sheets['Sheet1']
        sht_請求書様式.range('T6').value = 請求情報
        sht_請求書様式.range("T13").options(index=False).value = df_顧客請求データ
        wb_請求書様式.save(PATH + "\\data\\" + 顧客 + ".xlsx")
        create_pdf(wb_請求書様式.api, PATH + "\\pdf\\" + 顧客 + ".pdf")
        wb_請求書様式.close()
    wb_請求データ.close()

if __name__ == '__main__':
    # デバッグ用で、事前に'test.xlsm'を立ち上げて、test.py を実行することでデバッグできる。
    xw.Book('test.xlsm').set_mock_caller()
    請求書作成()

以上で、顧客毎にPOFの請求書が作成できます。コードの方は、Pandas を使うことで VBA を使うよりもすっきりしていると思います。

デバッグ

Excel の VBA の場合は、標準モジュール、クラスモジュール等 5 種類のモジュールがありますが、いずれも Excel の xlsm ファイルの中にシートのデータと合わして保存され、専用のエディターを使って編集をおこないますが、xlwings の場合は、別の独立した Pythonスクリプト (.py) ファイルになります。

スクリプトが別ファイルになることで、デバッガーがどうなるか心配する人もいるかもしれませんが、xlwings は、デバッグの機能も用意しています(公式ドキュメント Debugging)。

UDF の場合に、デバッガーを使いたい場合には、スクリプトに以下のコードを追加します。そのスクリプトファイルを起動すると、デバッグ用の COM サーバーが起動します。

if __name__ == '__main__':
    xw.serve()

それから、[xlwings] のリボンで、[Debug UDFs]にチェックをすると、UDF の処理にデバッグ用の COM サーバーを使うようになるので、デバッグができるようになります。
xw11.png

マクロの場合には、上の「請求書を作成するマクロを作る」のコードを参考にしてください。

VBA の場合には、あの古びた VBE(Visual Basic Editor)という専用のエディターを使わないといけないのですが、xlwings は、自分が日常使っている好みのエディターやデバッガーが使えるというのは大きなメリットです。なお、VBE の場合 Rubberduck という OSS のアドインを使うと使いやすくなるようです。

Excel に Python が搭載されることへの期待

自分の使い方であれば、現状の xlwings でも、大きな不満はありません。むしろ、Dynamic Arrays が使えるので便利かもと思っています。

そのため、Excel に対して現在期待しているのは Dynamic Arrays が早期にリリースされることです。Dynamic Arrays は、9月末の Microsoft Ignite で公表されましたが、最近のExcel関係ではかなり話題になったもので、一つの数式で複数のセルを表現できる機能です。上で説明したように xlwings では既に利用できるものですが、Excel の方に Dynamic Arrays がリリースされると処理も速くなるし操作も簡単になります。もちろん、Dynamic Arrays が導入されるのは、 Google Sheets だとそれができるからマイクロソフトとしては負けたくないためなのでしょうが、Python が搭載されるためには必ず必要になってくる機能です。なお、Dynamic Arrays については、以下のページが参考になると思います。

Preview of Dynamic Arrays in Excel
新しいExcelの関数
【新機能】エクセルのスピルでマトリックス表を作る
新しいExcelの一番の変更点は「スピル」?

また、マイクロソフトには Office Online もあるので、VBA の代替として JavaScript が使えるようにすることを急ぐ必要があることはわかります。ブラウザーでは JavaScript しか動かないのだから仕方ないです。それで、Excelチームの非公式回答に納得できました。

しかしながら、仕事で Excel を使っている人の場合は、マイクロソフト版の PyXLL のような製品が搭載されることに期待してるのではないでしょうか。企業の場合は、Excelのマクロが現場主導で勝手に開発・利用してきたものであることから管理に問題があるため撲滅したいと思っているところが多いと思います。VBA が 20 年も放置され、Python を搭載する要望も投票数は断トツであるにも関わらず放置されているのは、MS Office の大口利用者である企業の情シス部門からの要望が少ないからだと思っています。そうした状況で OSS の xlwings のインストールの許可をもらうの大変ですよね。

実際に、Excel と相性がいいのは JavaScript ではなくて、Range を直接操作できる行列演算が可能な Python, R, Julia です。Excel で Python, R, Julia が使えることの重要性は、マイクロソフトの Excel チームもわかっているはずです。非公式の回答には 'In the meantime' とあるので、将来的には Excel に Python が搭載されると思っていいのではないでしょうか。そう期待して、今から xlwings を使いましょう。マイクロソフトの製品では、Power BI Desktop でも Python と R が使えるようになっています。