【Python】openpyxlとxlwingsとの比較

エクセルファイルの操作で使われるライブラリopenpyxlとxlwingsについて、参考となるように比較のまとめです。

.NETでExcelを外部操作していたのですが、これまでの私個人の経験と異なる結果になっていました。また、書籍で受ける印象と全く異なり、比較するのもおこがましい、そんな感じのする結果です。

xlwingsとは

本家excelをプロセス間通信で制御するライブラリです。

プロセス間通信にCOMアーキテクチャが使われていて、COM通信を実現するのがpywin32です。pywin32は、Excel操作ライブラリとしてopenpyxlと比較されます。

そのため、xlwingsによるエクセル操作の特徴は、pywin32のものと同じになります。

openpyxlとxlwingsの違い

比較すると住処(すみか)が全く別だということが分かります。openpyxlは、Excelとの互換性に難ありですが、カバーできる環境が広くLinuxサーバでも動きます。xlwingsは、windowsユーザーがエクセルの自動処理をするのに向いています。

項目 openpyxl xlwings
動作環境 Linux / MacOS / Windows windows
Excel 不要 必要
サポートしているファイル形式 Excel 2010 の xlsx/xlsm/xltx/xltm Excelがサポートする形式全て
処理速度 〇 ×

openpyxlの問題点 計算式による結果が読み込めない事がある

Excelとの互換性を100%保障していなく、計算式が無視されるケースがあります。例えば、Excelで計算結果が表示されていても、openpyxlで値を拾えるとは限りません。日本で知られているのは和暦変換ですが、海外の掲示板でも、計算式が思ったように計算されていないことが報告されています。

openpyxlは、このように取得すれば、計算した値が拾える仕様になっています。

openpyxl.load_workbook('foo.xlsx', data_only=True)

しかし、openpyxlサポートされていない場合、取得値がなんとNoneとして処理されます。そして、当然ながらdata_only=Trueで計算式を無視して読み込んだものをエクセルファイルとして保存してしまうと、計算式が失われてしまいます。そうなったら、もはや目も当てられません。

openpyxlの問題点 行や列の挿入が実質利用不可能

Excelでは、行を追加すると上の行の情報が引き継がれます。

行挿入前

python openpyxlの問題点

Excelで2行目に挿入後

python openpyxlの問題点

2行目の背景色と書式は1行目と同じになり、計算式は1行目と同じになりません。(789は書式確認のため、手動で入力しています)

ところが、openpyxlでは、2行目が1行目と同じにならない上に、3行目の計算式が、Excelと違っているのです。

python openpyxlの問題点

xlwingsの問題点は、openpyxlの8倍の遅さ

Windows+Excel環境が必要という以外に問題点を挙げると「Windowsアップデートで挙動が変わる事がある」「とてつもなく遅い」というものがあります。

Office関連のWindowsアップデートがあると、その度に挙動の変化が起こるのでは、と気にかかります。ユーザーの要求通りに動いていたものが、アップデートと共に違った動作になることがあったのです。翌日のアップデートで動作が元に戻りましたが、それまでの間、動かすことができなくなりました。

それと、遅さです。pythonでopenpyxlとxlwingsで下記のような関数を作り処理時間を計測しました。

def excel_openpyxl() -> None:

    #   既存のBOOKを開く
    book = openpyxl.load_workbook('.\\openpyxl.xlsx')

    #   左から数えて1番目のシートを選択
    sheet = book.worksheets[0]

    now_time: str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    for row in range(10000):
        sheet.cell(row+1, 1).value = now_time

    #   保存
    book.save('.\\openpyxl.xlsx')

    book.close()

def excel_xlwings() -> None:
    #   エクセルの非表示を設定
    xlwings.App(visible=False)

    #   既存のBOOKを開く
    book = xlwings.Book('.\\xlwings.xlsx')

    #   左から数えて1番目のシートを選択
    sheet = book.sheets[0]

    now_time: str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    for row in range(10000):
        sheet.cells(row+1, 1).value = now_time

    #   保存
    book.save('.\\xlwings.xlsx')

    #   プロセスを削除
    book.close()

上の関数をそれぞれ10回走らせて、その平均値を取っています。ループ回数とは、10行目と29行目のrange(10000)の事です。

ループ回数 ライブラリ 平均処理時間(s) 左の標準偏差(s) 平均処理時間の倍率
10000回 openpyxl 1.54 0.04 –
10000回 xlwings 12.57 0.11 8.16
20000回 openpyxl 2.94 0.03 –
20000回 xlwings 24.53 0.68 8.34
30000回 openpyxl 4.3 0.02 –
30000回 xlwings 36.16 0.46 8.41
30000回分を一括設定 xlwings 2.36 0.47 0.55

実に、xlwingsはopenpyxlの8倍強の遅さです。openpyxlで1時間で終わるものが、xlwingsは8時間です。業務で使ったら、試行回数が限られるではありませんか。しかも、その間、1コアをMAXまで使う事になります。

この遅さの原因は、cells()の実行回数に比例して平均処理時間が増えていますので、明らかにCOMアーキテクチャによるプロセス間通信の回数です。従って、通信回数を減らすように見直せば速くなります。その見直し結果が、上表の最下行の「30000回分を一括設定」です。これは、excel_xlwings()を下記のように改め測定したものです。もうxlwingsが遅いとは思えません。

def excel_xlwings_range() -> None:
    #   エクセルの非表示を設定
    xlwings.App(visible=False)

    #   既存のBOOKを開く
    book = xlwings.Book('.\\xlwings.xlsx')

    #   左から数えて1番目のシートを選択
    sheet = book.sheets[0]

    now_time: str = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    values = [[now_time] for _ in range(30000)]
    sheet.range('a1:a30000').value = values # 配列で一括設定

    #   保存
    book.save('.\\xlwings.xlsx')

    #   プロセスを削除
    book.close()

openpyxlとxlwings、どっちを選ぶべきかの結論

openpyxlは、事務系では使えません。先のopenpyxlで分かる通り、データが消える可能性すらあります。それを防ごうとすると、一つ一つの関数についてExcelとの挙動の違いを確認しなければならないのです。

一方、xlwingsは、環境を選び速度の遅さはありますが、遅さはカバーできます。通信回数を減らすだけです。よって、カバーする労力は、openpyxlの挙動違いをカバーするそれと、比べ物にならないくらい少なく済みます。

どちらかを選べる状況だったら、迷うことなくxlwingsです。今後は、openpyxlからxlwingsにシフトする流れが加速すると思われます。

タイトルとURLをコピーしました