xlwings は、Python を使って Excel を直接操作したり、Excel から Python コードを呼び出したりできるライブラリです[BSD ライセンス]。VBA マクロの代替として Python で Excel の自動化(スクリプト作成、マクロ、ユーザー定義関数(UDF))を実現でき、Excel と Python のデータをシームレスに連携させ、双方の得意な部分を組み合わせて強力な自動化・データ処理を可能にします。
- PySide6 の GUI アプリから Excel を操作しようと、Excel とやり取りをする部分をスレッド化したところ、うまくいかずにハマってしまいました。
- 生成 AI から「PySide6 × xlwings を使う人が必ず通る“最大の罠”なんだよ」なんて言われていたのに、二度も同じ間違いをしてしまったので、備忘録にまとめました。
下記の OS 環境で動作確認をしています。
|
Windows 11 Pro | 25H2 |
| Excel | 2024 MSO | |
| Python | 3.13.9 | |
| PySide6 | 6.10.1 | |
| xlwings | 0.33.20 |
簡単なサンプル(GUI 無し)
まずは、xlwings を利用した、ごく簡単な GUI 無しのサンプルです。
import xlwings as xw
if __name__ == "__main__":
wb = xw.Book() # 新しいワークブックを開く
sheet = wb.sheets["Sheet1"] # シート・オブジェクトをインスタンス化
sheet["A1"].value = "Python から書き込みました。" # 値の書き込み
GUI サンプル
GUI のプッシュボタンをクリックして、簡単なサンプル(GUI 無し)と同じ処理を実行するサンプルです。
このサンプルは Excel へ文字列を書き込むだけですが、複雑な処理を加えても GUI のイベントループに影響を与えないように、「別スレッド」で Excel へアクセスする処理をするようにしています。
import sys
import xlwings as xw
from PySide6.QtCore import (
QObject,
QThread,
Signal,
Slot,
)
from PySide6.QtGui import QCloseEvent
from PySide6.QtWidgets import (
QApplication,
QPushButton,
QVBoxLayout,
QWidget,
)
class ExcelWorker(QObject):
def __init__(self):
super().__init__()
self.sheet = None
@Slot()
def initWorker(self):
wb = xw.Book() # 新しいワークブックを開く
self.sheet = wb.sheets["Sheet1"] # シート・オブジェクトをインスタンス化
@Slot(str)
def excel_write(self, msg: str):
self.sheet["A1"].value = msg # 値の書き込み
class SampleXlwings(QWidget):
requestWorkerInit = Signal()
requestWrite = Signal(str)
def __init__(self):
super().__init__()
# xlwings用スレッド
self.thread = thread = QThread()
self.worker = worker = ExcelWorker()
worker.moveToThread(thread)
thread.started.connect(self.requestWorkerInit.emit)
self.requestWorkerInit.connect(worker.initWorker)
self.requestWrite.connect(worker.excel_write)
thread.start()
# GUI
layout = QVBoxLayout()
self.setLayout(layout)
but = QPushButton("新規 Excel へ文字列を書き込む")
but.clicked.connect(self.request_write)
layout.addWidget(but)
def closeEvent(self, event: QCloseEvent):
if self.thread is not None:
self.thread.quit()
self.thread.wait()
if self.worker is not None:
self.worker.deleteLater()
self.worker = None
event.accept()
@Slot()
def request_write(self):
msg = "Python から書き込みました。"
self.requestWrite.emit(msg)
def main():
app = QApplication(sys.argv)
win = SampleXlwings()
win.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
なお、PySide6 では @Slot のデコレータを省略しても動作します。
GUI サンプルを実行すると、Excel の新しいワークブックが開きます。
新規 Excel へ文字列を書き込む ボタンをクリックすると、「簡単なサンプル」と同じように、文字列が Excel シートに書き込まれます。
注意点
ExcelWorker クラスのコンストラクタ __init__ メソッドで、以下のように xlwings のインスタンスを定義すると動作しません。
class ExcelWorker(QObject):
def __init__(self):
super().__init__()
wb = xw.Book() # 新しいワークブックを開く
self.sheet = wb.sheets["Sheet1"] # シート・オブジェクトをインスタンス化
この場合、下記のようなエラーが出ます。
pywintypes.com_error: (-2147417842, 'アプリケーションは、別のスレッドにマーシャリングされたインターフェイスを呼び出しました。', None, None)
メインスレッドで ExcelWorker のインスタンスを定義するので、ExcelWorker.__init__ も当然、「メインスレッド」で実行されます。
「別スレッド」で Excel とやり取りをしたければ、「別スレッド」を thread.start() で開始した後に、その「別スレッド」内で xlwings のインスタンスを定義する必要があります。
そういうわけで、「別スレッド」が開始されたときに初期化用のメソッド ExcelWorker.initWorker を実行するようにしています。
self.thread = thread = QThread() self.worker = worker = ExcelWorker() worker.moveToThread(thread) thread.started.connect(self.requestWorkerInit.emit) # 「別スレッド」が開始されればシグナルを発行 self.requestWorkerInit.connect(worker.initWorker) self.requestWrite.connect(worker.excel_write) thread.start() # 「別スレッド」開始
@Slot()
def initWorker(self):
wb = xw.Book() # 新しいワークブックを開く
self.sheet = wb.sheets["Sheet1"] # シート・オブジェクトをインスタンス化
参考サイト
- xlwings Documentation
- ASCII.jp:COM(Component Object Model)は古い技術だが、いまだに現役 あらためて解説する (1/2) [2023-04-23]
にほんブログ村
#オープンソース




0 件のコメント:
コメントを投稿