はじめに
退屈なExcel作業をPythonにやらせるためのモジュールの選び方に書いた通り、PythonでExcel作業を自動化するためにpywin32を利用した場合の問題の一つが、なんらかの理由でプログラムが異常終了してしまうとオープンしたExcelファイルをロックしてしまうことでした。異常終了ではなくても、うっかりファイルのクローズやExcelを終了しないままでプログラムを終了してしまうと、気がつくとタスクマネージャーにExcelのプロセスがずらっと並ぶことになってしまいます。
Pythonなので、with文で実行コンテクストを利用すれば簡単に解決すると思ったんですけど、簡単には解決できないことがわかりました。
問題点
問題は、pywin32では同じExcelのファイルをオープンしたときに同じオブジェクトが返ることです。同じオブジェクトが返るだけならいいんですけど、そのオブジェクトをどこかでクローズしたら、同じファイルをオープンしたときに返ってきたオブジェクトが、すべてクローズされてしまいます。
言葉での説明だとわかりにくいかもしれないので、簡単なサンプルプログラムを作成しました。同じファイルを2回オープンして、別の変数に保管します。その変数の比較と、それぞれのオブジェクトをクローズするプログラムです。
import os
import win32com.client
xlsx = win32com.client.Dispatch('Excel.Application')
book1_1 = xlsx.Workbooks.Open(os.path.abspath('サンプルデータ1.xlsx'))
book1_2 = xlsx.Workbooks.Open(os.path.abspath('サンプルデータ1.xlsx'))
book2 = xlsx.Workbooks.Open(os.path.abspath('サンプルデータ2.xlsx'))
if book1_1 == book1_2:
print("同じファイルのオブジェクトは同じ")
else:
print('同じファイルのオブジェクトは違う')
if book1_1 == book2:
print("違うファイルのオブジェクトは同じ")
else:
print('違うファイルのオブジェクトは違う')
book1_1.Close()
book2.Close()
book1_2.Close()
xlsx.Quit()
このプログラムを実行した結果が以下の通りです。
同じファイルのオブジェクトは同じ
違うファイルのオブジェクトは違う
Traceback (most recent call last):
File "test1.py", line 21, in <module>
book1_2.Close()
File "<COMObject Open>", line 3, in Close
pywintypes.com_error: (-2147417848, '起動されたオブジェクトはクライアントから切断されました。', None, None)
book1_1とbook1_2は、同じExcelファイルを別々にオープンして返ってきたオブジェクトですが、比較すると同じですし、book1_1をクローズした後でbook1_2をクローズすると、ご覧の通りにエラーになることがわかりました。
念のため、別のファイルをオープンしてbook2に代入したオブジェクトは、book1_1をクローズした後にクローズしましたが、問題はありませんでした。
まとめ
一つのプログラムで同じファイルを複数回オープンしても、クローズは1回でないとエラーになるという仕様だということがわかりました。ファイルごとに、オープンされた回数をカウントして、クローズするとオープンされた回数を減らすような管理をして、オープンされた回数が0になったらオブジェクトをクローズする仕組みが必要だということですね。