はじめに
F#(fsi)によるExcelの操作をまとめたので,IronPythonでの操作も備忘録としてまとめておきます。
「python excel」をキーワードに検索すると「openpyxl」など,いくつかのライブラリがあがってきます。
それぞれ優秀なライブラリですが,VBAからの移行にはそれなりの学習コストを伴います。
その点でIronPythonはハードルが低いので入門者の選択肢の一つになると思います。
IronPythonのメリット(個人の感想)
- ほぼVBA(Microsoft.Office.Interop.××× 使用時)
- .NETの活用ができる(対話環境は.NET入門者の学習にも役立つ)
デメリット
-
Python2.7であるIronPython3.4(バージョン 3.4.0-alpha1)がリリースされました。 - 外部ライブラリが使えない(と思っていてください)
- COMオブジェクトの解放問題(後述)
目的は,Excelの単純作業でちょっとした自動化をはかることですのでデメリットは気にしていません。
対話モードではTab補完も効きますし,dir関数やhelp関数もそれなりに役立ちます。
IronPythonのインストールやドキュメントは公式サイトなどをご覧ください。
とりあえず,前編としてVBAからの移行する際のポイントをまとめてみました。
作業環境
Windows 10 Pro(Ver. 20H2)
Excel for Microsoft 365 MSO 32bit
IronPython 2.7.11 (2.7.11.1000)
.NET Framework 4.8.4341.0 (64-bit)
IronPythonとExcelの接続
注意:IronPythonによる操作は元に戻すことができないので,既存ファイルを操作する場合は事前のバックアップをおすすめします。
以下,対話モードでの実行を前提とします。
(1) IronPythonからExcelを起動する例
import System # 必須ではないが必要な場面多し
import clr
clr.AddReference('Microsoft.Office.Interop.Excel')
from Microsoft.Office.Interop.Excel import *
from System.Runtime.InteropServices import Marshal # 後で使うのでインポートしておく
ex = ApplicationClass(Visible = True)
wb = ex.Workbooks.Add() # 新規作成の場合
# 既存ファイルを開く場合
# path = r'C:\Users\user1\Desktop\sample.xlsx'
# wb = ex.Workbooks.Open(path)
ws = wb.Worksheets[1]
(2) すでに開いているExcelと接続する場合(ActiveSheetをターゲットにする例)
import System
import clr
clr.AddReference('Microsoft.Office.Interop.Excel')
from Microsoft.Office.Interop.Excel import *
from System.Runtime.InteropServices import Marshal
ex = Marshal.GetActiveObject('Excel.Application')
wb = ex.ActiveWorkbook
ws = ex.ActiveSheet
単独セルの操作
値の設定
対象のワークシート指定が必須なことを除けば,ほぼVBAと同様です。
Value
プロパティの省略などもVBAに沿った扱いができます。
ただし,Excelの各オブジェクトについて,プロパティの引数は角かっこ[]で囲みます。
注意点がありますので,詳しくは後述します。
ws.Cells[1, 1] = 123
ws.Cells[2, 'A'] = 'ABC'
ws.Cells['1', 'B'] = 456
ws.Range['B2'] = 'DEF'
ws.Range['A3'] = '2021/4/1' # 日付
ws.Range['A4'] = '令和3年4月1日' # 日付
ws.Range['A5'] = 'R3.4.1' # 文字列
ws.Range['A6'] = System.DateTime.Parse('R3.4.1') # 日付(要import System)
余談ですが,セルA5の代入はws.Range['A5'] = '4.1.R3'
とすると,日付データになります。
値の取得
セルの値を取得する場合は,Value
プロパティを使いますが引数を要するので()をつけます。
値が数値または文字列であればValue2
プロパティでも同じ結果ですが,Value2
プロパティには引数がないので()はつけません。
セルA1とA3の値をそれぞれのプロパティで取得します。
val_A1 = ws.Range['A1'].Value()
val2_A1 = ws.Range['A1'].Value2
val_A3 = ws.Range['A3'].Value()
val2_A3 = ws.Range['A3'].Value2
結果を確認します。
>>> val_A1
123.0
>>> val2_A1
123.0
>>> val_A3
<System.DateTime object at 0x000000000000006F [2021/04/01 0:00:00]>
>>> val2_A3
44287.0
セルA3の値からわかるように,日付データをDateTime
オブジェクトとして処理する場合はValue
プロパティを使用します。
なお,日付データの扱いは.NETとの関係として後編で触れます。
()と[]
VBAからIronPythonに移行する際に引っかかったのが,Range
やCells
,Value
プロパティのように引数を要するプロパティでの()と[]の使い分けです。
以下,自己流の整理です。
- 引数は基本的に[]で囲む。
- ただし,引数を省略する(既定値を使用する)場合,空の[]は文法エラーになる。
- ()で囲んでもプロパティの値を返す。(関数呼び出しと見なして実行する?)
- 空の()は引数省略(既定値)として値を返し,文法エラーにならない。
- 代入文によるプロパティの設定では左辺に()を使うことができない。(関数呼び出しなので。)
IronPythonでのRangeオブジェクトの扱い
VBAの公式ドキュメントでは,次の「注釈」があります。IronPython上でRange
オブジェクトを扱うときも同様に動作します。
ここまでの検証
()や[]の扱いも含めて,検証してみます。
例1(左辺()の場合)
>>> ws.Range('D1') = 999 # 左辺は関数呼び出しだから代入できない
File "<stdin>", line 1
SyntaxError: can't assign to function call
>>> ws.Range('D1')[1] = 999 # 左辺はws.Range('D1').Item[1].Valueなので代入可
>>>
例2(Value
を明示してみる)
>>> ws.Range['D1'].Value[] # 文法エラー
File "<stdin>", line 1
ws.Range['D1'].Value[]
^
SyntaxError: unexpected token ']'
>>> ws.Range['D1'].Value[None] # とりあえず引数1つを与えておけばエラーなく働く
999.0
>>> ws.Range['D1'].Value() # 「セルの値を返す関数」として実行した状態
999.0
>>> ws.Range['D1'].Value # かっこ無しだとオブジェクトを返す
<Microsoft.Scripting.ComInterop.DispCallable object at 0x000000000000007A [<bound dispmethod Value>]>
>>> _() # 得られたオブジェクトを関数として実行
999.0
ws.Range['D1'].Value[None]
は,検証のために試しましたが推奨するものではありません。
Value
プロパティの既定の引数を省略せずに明記すると
ws.Range['A1'].Value[XlRangeValueDataType.xlRangeValueDefault]
です。
またws.Range['A1'].Value[System.Type.Missing]
で「既定値の使用」を明示することもできます。
例3(Value
の省略)
>>> ws.Range['D1']() # ws.Range['D1'].Value()と等価
999.0
>>> ws.Cells[1, 'D']()
999.0
注目点は,**「Range
オブジェクトの後に()をつけるとValue
が得られる」**ということです。
後編で応用してみます。
例4(動作を細かく調べてみる)
>>> ws.Range
<Microsoft.Scripting.ComInterop.DispCallable object at 0x0000000000000078 [<bound dispmethod Range>]>
>>> _['D1']
<System.__ComObject object at 0x0000000000000079 [System.__ComObject]>
>>> _()
999.0
その他,Range.Address
プロパティなども引数をとるので同様に確認することができます。
以上,引数を要するプロパティについて()と[]の使い分けなどを確認してみました。
蛇足ですが,メソッドはそもそもオブジェクトに作用する関数と見なせますので引数をとらない場合も末尾の()が必要になります。
例5(プロパティとメソッド)
>>> ws.Range['A1'].Select() # Selectメソッドの実行(成功するとTrueを返す)
True
>>> ex.Selection() # 見た目はメソッドだがex.Selection.Value()と等価
123.0
後始末について
IronPythonにおける「COMオブジェクトの解放問題」について,自身の環境下における結果だけをまとめておきます。
<結論>
「Excelを閉じた後,Marshal.FinalReleaseComObject(ex)
を実行して,ipy.exeを終了する。」
注:ex
は,IronPythonと接続されたExcelの名前です。
この<結論>は,タスクマネージャーでバックグラウンドプロセスが残るか否かだけに絞った話ですので,安全性を保証するものではありません。
Excelを閉じると(自動・手動問わず),Excelはバックグラウンドプロセスに移ります。
Marshal.FinalReleaseComObject(ex)
を実行してもバックグラウンドプロセスには残ったままですが,ipy.exeを終了するとほとんど間を置かずにバックグラウンドプロセスから消えます。
Marshal.FinalReleaseComObject(ex)
を実行せずにipy.exeを終了すると,Excelがバックグラウンドプロセスに残ります。(タスクマネージャーで終了可能)
個人的には,次の手順をコピペして終了させています。
wb.Close()
# wb.Close(True) # 上書きして閉じる
# wb.Close(False) # 保存せず閉じる
ex.Quit()
Marshal.FinalReleaseComObject(ex)
この点はF#(fsi)の場合と大きく異なります。
セル範囲の扱いなど,後編に続きます。