pywin32をExcel操作にサッと使う時のテンプレート

# はじめに
pywin32をExcel操作にサッと使いたいときのテンプレートを、主に自分用に残しておきます。

ときどきpywin32を使って何かしようと思うと、個人的に大体以下をやらかしてしまいます。
- 例外などで途中終了した際に、Excelアプリケーションのゾンビが増えていく
- アプリケーションを開いた状態で実行すると、Excelが勝手に動きまわり画面が奪われる

たまに思い立って何か作ろう！となったときに、これらのやらかしを未然に防げるよう、あらかじめテンプレートを用意しておこうと思います。

テンプレートの内容は以下になります。
- Excelがすでに起動していたら閉じるよう警告する
- 既存のExcelファイルを開いて何かする
- 新規にExcelファイルを作って何かする

:::note warn
本記事で紹介するスクリプトを実行される際は、事前に`pywin32`をインストールしておいてください。
:::

# テンプレート
さっそく、作ったものを貼っておきます。

```pywin32_simple_template.py
import win32com.client
from pathlib import Path


# Excelがすでに起動していたら閉じるよう警告する
try:
    if win32com.client.GetObject(Class='Excel.Application'):
        raise RuntimeError('Close all Excel applications!')

except win32com.client.pywintypes.com_error:
    pass


# 既存のExcelファイルを開いて何かする
file_path = Path('読み込みたいファイル.xlsx')
file_name = str(file_path.absolute())

try:
    app = win32com.client.Dispatch('Excel.Application')
    wb = app.Workbooks.Open(file_name)

    # ↓↓↓ 処理を書く ↓↓↓

    # ↑↑↑ 処理を書く ↑↑↑

finally:
    wb.Close()
    app.Quit()


# 新規にExcelファイルを作って何かする
file_path = Path('書き込みたいファイル.xlsx')
file_name = str(file_path.absolute())

try:
    app = win32com.client.Dispatch('Excel.Application')
    wb = app.Workbooks.Add()

    if file_path.is_file():
        raise RuntimeError(f'{file_name} already exists!')

    # ↓↓↓ 処理を書く ↓↓↓

    # ↑↑↑ 処理を書く ↑↑↑

    wb.SaveAs(Filename=file_name)

finally:
    wb.Close()
    app.Quit()
```



# 説明
## Excelがすでに起動していたら閉じるよう警告する
pywin32のスクリプトを実行した際に、先にExcelが起動していると、pywin32に目まぐるしく操作されるExcelアプリが表示されて、しばらく手が出せなくなってしまいます。私は非表示で処理して欲しい人なので、Excelが暴れまわるとかなり焦ります…。

今回はそうならないように、割り切ってExcelアプリが起動していたら閉じるよう警告を出し、処理を中断する制御を入れました。ちょっと面倒にはなりますが、編集中の他のExcelファイルがうっかり閉じられるといった、思いがけない事故も減らせるのかなと思います。
```python
# Excelがすでに起動していたら閉じるよう警告する
try:
    if win32com.client.GetObject(Class='Excel.Application'):
        raise RuntimeError('Close all Excel applications!')

except win32com.client.pywintypes.com_error:
    pass
```

(警告の様子)
![close.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653390/47662bfa-c097-0f87-5f64-83be46536850.png)

`win32com.client.GetObject(Class='Excel.Application')`の部分で、起動中のExcelを取得しに行っています。取得できたら、「Excel起動中」ということなので、`RuntimeError`の例外を投げて処理を中断しています。逆に取得できない場合は、pywin32が`pywintypes.com_error`を投げるので`except`で受けて処理を継続させています。


## 既存のExcelファイルを開いて何かする
既存のファイル開いて何かする際のテンプレートです。

```python
# 既存のExcelファイルを開いて何かする
file_path = Path('読み込みたいファイル.xlsx')
file_name = str(file_path.absolute())

try:
    app = win32com.client.Dispatch('Excel.Application')
    wb = app.Workbooks.Open(file_name)

    # ↓↓↓ 処理を書く ↓↓↓

    # ↑↑↑ 処理を書く ↑↑↑

finally:
    wb.Close()
    app.Quit()
```

ファイルパスは絶対パスで指定が必要なので、`pathlib`を使用しました。

Excelファイルを読み込んで何か処理をした際、途中で例外が発生すると、アプリが開いたままとなります。アプリを非表示にしていると目には見えないのですが、実際はタスクが残っている状態になります。知らずに何度も動作させると大量の見えないタスクがゾンビのように増えていきます。嫌ですよねぇ…。

ということで、pywin32の処理はまるっと`try`に記述し、Excelアプリの終了処理は`finally`に記述しています。途中終了してもかならず`wb.Close()`(ブックを閉じる)と`app.Quit()`(Excelアプリを終了する)が呼ばれるので、めでたくゾンビの増殖を阻止できました。


## 新規にExcelファイルを作って何かする
新規にファイルを作って何かする際のテンプレートです。

```python
# 新規にExcelファイルを作って何かする
file_path = Path('書き込みたいファイル.xlsx')
file_name = str(file_path.absolute())

try:
    app = win32com.client.Dispatch('Excel.Application')
    wb = app.Workbooks.Add()

    if file_path.is_file():
        raise RuntimeError(f'{file_name} already exists!')

    # ↓↓↓ 処理を書く ↓↓↓

    # ↑↑↑ 処理を書く ↑↑↑

    wb.SaveAs(Filename=file_name)

finally:
    wb.Close()
    app.Quit()
```

既存のファイルを読み込む場合とほぼ同じです。大きな違いは書き込みファイルが既に存在している場合は、処理を終了させるようにしている点です。

(警告の様子)
![already.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2653390/21b0724e-642a-649d-51d1-3f959dbc43c5.png)



# おわりに
pywin32をそこまで頻繁に使うわけではなかったので、これまでは毎回調べなおしてその場しのぎをしておりました。今回の記事を機に、自分なりにまとめることができてよかったです。

今後も何か見つけたらテンプレートを充実させていきたいです。