Calc(3)セルへのPythonのシーケンス型アクセス法

2017-08-27

旧ブログ

t f B! P L
linuxBean14.04(131)LibreOfiice5.2のインストールでみたようにLibreOffice5.1から新しくなったPyUNOでのセルへのアクセス方法を確認したあとに、複数セルにまとめて書き込みます。

前の関連記事:Calc(2)課題2:選択範囲の行列番号をメッセージボックスに表示


セルへのPythonのシーケンス型アクセス法


MatthewFrancisPyUNO.pdf

このスライドがとても参考になります。

印刷して傍らに置いて参考にしています。

セルへのアクセスする方法はgetCellByPosition()や、getCellRangeByPosition()、getCellRangeByName()に代わって行と列のインデックスのスライスでアクセスできるようになりました。

これに基づいてCalc(1)課題1:LibreOffice CalcのPythonマクロを動かすのマクロを書き換えます。
def macro():
 doc = XSCRIPTCONTEXT.getDocument()
 if not doc.supportsService("com.sun.star.sheet.SpreadsheetDocument"):  # Calcドキュメントであることを確認する。
  raise RuntimeError("Please execute this macro with a Calc document.")
 sheets = doc.getSheets()
 sheet = sheets[0]
 cellrange = sheet[:5,:2]  # iからjへのスライスはi<=k<jとなるようなインデクスkを持つ要素。iは0から開始。
 cellrange.clearContents(VALUE+DATETIME+STRING+ANNOTATION+FORMULA) 
 cellrange[0,0].setString('みかん')
 cellrange[0,1].setString('りんご')
 subrange = cellrange[1:5,:2]
 for i in range(len(subrange.Rows)):
  for j in range(len(subrange.Columns)):
   subrange[i,j].setValue((i+1)*(j+2)) 
g_exportedScripts = macro, #マクロセレクターに限定表示させる関数をタプルで指定。
(モジュールのインポートは省いてあります。必要なモジュールをインポートした完成版はこの記事の一番下のGitHubへのリンク先にあります。)

Calc(1)課題1:LibreOffice CalcのPythonマクロを動かすのときはcellrangeは行1から行6、列Aから列Cまで取得していましたが、今回は入力する範囲だけの行1から行5、列Aから列Bまでだけ取得しています。

7行目のsheet[:5,:2]のスライスの1番目の要素は行のインデックスを表し、2番目の要素は列のインデックスを表します。

インデックスは0から始まります。

:5は0:5のことでインデックス0以上5未満を意味するのでつまり行1から行5を表します。

:2は0:2のことでインデックス0以上2未満を意味するのでつまり列Aから列Bを表します。

11行目のsubrangeはcellrangeに対する相対範囲のスライスを表します。

(2017.10.29追記。sheet[i, j]の方法はijいずれかをスライスで指定したときと、ともにインデックスで指定したときで、戻り値の型が異なるので注意が必要です。Calc(32)sheet["A1"]とsheet[0, 0]とsheet[0:1, 0]の違い参照。)

メソッドをインターフェイスのアトリビュートに置き換えるメリットとデメリット


デバッガでみるとUNOオブジェクトにはAPIリファレンスには載っていない属性があることがわかります。

これらの属性はインターフェイスのアトリビュートです。
def macro():
 doc = XSCRIPTCONTEXT.getDocument()
 if not doc.supportsService("com.sun.star.sheet.SpreadsheetDocument"):  # Calcドキュメントであることを確認する。
  raise RuntimeError("Please execute this macro with a Calc document.")
 sheets = doc.Sheets
 sheet = sheets[0]
 cellrange = sheet[:5,:2]  # iからjへのスライスはi<=k<jとなるようなインデクスkを持つ要素。iは0から開始。
 cellrange.clearContents(VALUE+DATETIME+STRING+ANNOTATION+FORMULA) 
 cellrange[0,0].String = 'みかん'
 cellrange[0,1].String = 'りんご'
 subrange = cellrange[1:5,:2]
 for i in range(len(subrange.Rows)):
  for j in range(len(subrange.Columns)):
   subrange[i,j].Value = (i+1)*(j+2)
getSheets()、setString()、setValue()、といったメソッドをそれぞれSheets、String、Valueという属性でのアクセスに置き換えました。

getCellByPosition()や、getCellRangeByPosition()、getCellRangeByName()に代わってスライスを使う方法はわかりやすくなってメリットを感じます。

しかしそれ以外のメソッドを属性でのアクセスに置き換えるメリットは何でしょう。

属性のうち、APIリファレンスで検索できるのはプロパティだけです。

UNOでは、プロパティはサービスの属性で、インターフェイスの属性はアトリビュートになります(linuxBean14.04(170)Pythonのデスクリプタの学習を少し参照)。

アトリビュートはAPIリファレンスには載っていないので、使えるアトリビュートを調べるには、いちいちデバッガとかインスペクタを使わないといけません。

それでもアトリビュートを使う理由についてデベロッパーガイドに書いてありました。

Strictly speaking, interface attributes are not needed in UNO. Each attribute could also be expressed as a combination of one method to get the attribute's value, and another method to set it (or just one method to get the value for a read-only attribute). However, there are at least two good reasons for the inclusion of interface attributes in UNO: First, the need for such combinations of getting and setting a value seems to be widespread enough to warrant extra support. Second, with attributes, a designer of an interface can better express nuances among the different features of an object. Attributes can be used for those features that are not considered integral or structural parts of an object, while explicit methods are reserved to access the core features.
(訳:  厳密に言えば、UNOではインターフェイスのアトリビュートは必要ありません。 各アトリビュートは、アトリビュートの値を取得する1つのメソッドと、それを設定する別のメソッド(または読み取り専用アトリビュートの値を取得する1つのメソッド)の組み合わせとして表現することもできます。 しかし、UNOにインターフェイスアトリビュートがあるのには、少なくとも2つの理由があります。まず、値の取得と設定の組み合わせの必要性は、余分なサポートを保証するのに十分普及しているようです。 次に、アトリビュートを使用して、インタフェースの設計者はオブジェクトのさまざまな特徴のニュアンスをよりよく表現できます。 アトリビュートは、オブジェクトの一体的または構造的部分と見なされない機能に使用できますが、明示的なメソッドはコア機能にアクセスするために予約されています。)
Objects, Interfaces, and Services - Apache OpenOffice Wiki

つまりは値の取得と設定に同じ記法を使うアトリビュートは広く普及していることと、アトリビュートによってオブジェクトの特徴を表現しやすい、というメリットがあるようです。

確かにデバッガをみるといちいち実行しないと値が取得できないメソッドと違って属性はその値がVariablesビューで見れるのでオブジェクトの特徴をとらえやすいです。

メソッドを属性(のうちアトリビュート)でのアクセスに置き換える私にとっての最大のデメリットはAPIリファレンスで検索しにくくなることです。

メソッドならAPIリファレンスの検索窓にメソッド名入力すれば、そのメソッドの使い方だけでなく、どのインターフェイスのメソッドなのかもわかります。

ソースを読解するときにはとても便利です。

今度は先ほどと逆に、シーケンス型アクセスで置換できるメソッド以外は属性ではなくメソッドに書き換えてみました。
def macro():
 doc = XSCRIPTCONTEXT.getDocument()
 if not doc.supportsService("com.sun.star.sheet.SpreadsheetDocument"):  # Calcドキュメントであることを確認する。
  raise RuntimeError("Please execute this macro with a Calc document.")
 sheets = doc.getSheets()
 sheet = sheets[0]
 cellrange = sheet[:5,:2]  # iからjへのスライスはi<=k<jとなるようなインデクスkを持つ要素。iは0から開始。
 cellrange.clearContents(VALUE+DATETIME+STRING+ANNOTATION+FORMULA) 
 cellrange[0,0].setString('みかん')
 cellrange[0,1].setString('りんご')
 subrange = cellrange[1:5,:2]
 for i in range(len(subrange.getRows())):
  for j in range(len(subrange.getColumns())):
   subrange[i,j].setValue((i+1)*(j+2)) 
PyUNOでの新しい記法以外のところはAPIリファレンスに載っているメソッドを使うとこのようになります。

Pythonにもアトリビュートとプロパティの区別があり、プロパティは速度が落ちます(linuxBean14.04(170)Pythonのデスクリプタの学習を少し参照)。

しかしUNOでのアトリビュートの速度についての言及したものは見つけられませんでした。

UNOはIDLを定義しているだけなので、速度は実装に依存するので一概にはどちらが速いのかはいえないのだと思います。

setDataArray()で複数セルへまとめて書き込む


setDataArray()の引数は行のセルを要素とするタプルのタプルになります。

タプルでなくても、リストのリストでもイテレータでも書き込めるようです。
def macro():
 doc = XSCRIPTCONTEXT.getDocument()
 if not doc.supportsService("com.sun.star.sheet.SpreadsheetDocument"):  # Calcドキュメントであることを確認する。
  raise RuntimeError("Please execute this macro with a Calc document.")
 sheets = doc.getSheets()  # ドキュメントからSpreadsheetsを取得。
 sheet = sheets[0]  # Spreadsheetを取得。
 sheet.clearContents(VALUE+DATETIME+STRING+ANNOTATION+FORMULA)  # シートの全内容をクリア。
 sheet[0,:2].setDataArray((('みかん', 'りんご'),))  # 引数は行のセルを要素とするタプルのタプル。
 cellrange = sheet[1:5,:2]  # SheetCellRangeを取得。
 cellrange.setDataArray([[(i+1)*(j+2) for j in range(len(cellrange.getColumns()))] for i in range(len(cellrange.getRows()))])  # 引数はタプル以外にリストでもイテレータでもOKだが、シートの範囲と大きさと一致していないといけない。
g_exportedScripts = macro, #マクロセレクターに限定表示させる関数をタプルで指定。8行目と10行目でsetDataArray()を使っています。
setDataArray()の引数の二次元タプルは書き込み先のSheetCellRangeと次元が一致していないとエラーになります。
sheet[0,:2].setDataArray((('みかん', 'りんご'),))  # 引数は行のセルを要素とするタプルのタプル。
sheet[0,:2]は行1(インデックス0)、列A列B(インデックス0以上2未満)の範囲を取得しています。

なのでsetDataArray()の引数のタプルは、要素2個(=1行当たりの列数)のタプル1個(=行数)のタプルになります。
 cellrange = sheet[1:5,:2]  # SheetCellRangeを取得。
 cellrange.setDataArray([[(i+1)*(j+2) for j in range(len(cellrange.getColumns()))] for i in range(len(cellrange.getRows()))])  # 引数はタプル以外にリストでもイテレータでもOKだが、シートの範囲と大きさと一致していないといけない。
sheet[1:5,:2]は、行2から行5(インデックス1以上5未満)、列A列B(インデックス0以上2未満)の範囲を取得しています。

この範囲の行数は4、列数は2なので、setDataArray()の引数のタプルは、要素数2のタプル4個のタプルにしないといけません。

タプルで無くてもリストでよいので、リストの内包表記を使っています。

列を並べる方法は受け付けてくれないので、行を並べるようにしないといけません。

In [1]:
for i in range(4):
      for j in range(2):
           (i+1)*(j+2)
iが行、jが列を表します。
これを2x4の多次元リストにします。
リストの内包表記にするには最後の行を先頭に持ってきて、一行にします。
In [2]:
[(i+1)*(j+2) for i in range(4) for j in range(2)]
Out[2]:
[2, 3, 4, 6, 6, 9, 8, 12]
これだと1次元なのでダメです。
最初のforループをまずリストにしてみます。
In [3]:
[[(i+1)*(j+2) for i in range(4)] for j in range(2)]
Out[3]:
[[2, 4, 6, 8], [3, 6, 9, 12]]
これだと列の要素を並べたリストになるのでダメです。
行の要素を並べたリストにするにはまず列のループから先に回して行に相当するリストを作ってそれをリストにします。
In [4]:
[[(i+1)*(j+2) for j in range(2)] for i in range(4)]
Out[4]:
[[2, 3], [4, 6], [6, 9], [8, 12]]
これでうまく2x4のリストができました。

これで列2x行4のセルの範囲にsetDataArray()で書き込めます。

Calc/cellaccesstest.py at f8ef5eec1d84391ccc41dcc43e425ec4c4bc7365 · p--q/Calc

このcellaccesstest.pyは必要なモジュールのインポートに加えて、オートメーションのためのコードとリモートデバッグのためのコードも含んでいます。

参考にしたサイト


MatthewFrancisPyUNO.pdf
LibreOffice5.1から導入されたセルへのPythonのシーケンス型アクセス法の解説。

Objects, Interfaces, and Services - Apache OpenOffice Wiki
UNOの属性の解説があります。

次の関連記事:Calc(4)SDKのJavaの例を実行してみる

ブログ検索 by Blogger

Translate

最近のコメント

Created by Calendar Gadget

QooQ