LibreOffice(37)マクロの記録をPythonに翻訳3:クラスを使う

ラベル: ,

前の関連記事:LibreOffice(36)マクロの記録をPythonに翻訳2:反復部分を関数にする


今度はクラスを使ってマクロの記録を書き換えてみます。

関数をクラスの__init__()メソッドに変える

from com.sun.star.beans import PropertyValue
def dispatch_writer_exp():
    FnDispatch("InsertText", ["Text", "例文"])
    FnDispatch("InsertPara")
    FnDispatch("InsertText", ["Text", "新しい段落に"])
    FnDispatch("Bold", ["Bold", True])
    FnDispatch("InsertText", ["Text", "太字の"])
    FnDispatch("Bold", ["Bold", False])
    FnDispatch("InsertText", ["Text", "単語を含める"])
class FnDispatch:
    def __init__(self, command, m_args=[]):
        frame = XSCRIPTCONTEXT.getDesktop().getCurrentFrame()
        ctx = XSCRIPTCONTEXT.getComponentContext()
        dispatcher = ctx.getServiceManager().createInstanceWithContext("com.sun.star.frame.DispatchHelper",ctx)
        if m_args:
            n_args = len(m_args)//2
            args = [PropertyValue() for i in range(n_args)]
            for i in range(n_args):
                args[i].Name = m_args[i*2]
                args[i].Value = m_args[i*2+1]
            args = tuple(args)
        else:
            args = tuple()
        dispatcher.executeDispatch(frame, ".uno:" + command, "", 0, args)
Pythonのクラス名は慣習に従ってつなげた単語の先頭文字を大文字にするキャメルケースにします。(4.8. 間奏曲: コーディングスタイル

それに対して関数名やメソッド名に使うアンダーバーで単語をつなげる方法をスネークケースといいます。(キャメルケース - Wikipedia

ということで関数fn_dispatch()FnDispatchというクラスにしました。

LibreOffice(10)オブジェクト指向プログラミングのお勉強:総論でみたようにクラスは抽象的なものなので具体化(インスタンス化)してオブジェクト(インスタンス化して生成したオブジェクトをとくにインスタンスという)にして使わないといけません。

Pythonではクラスのインスタンス化は関数のように、クラス名()、でインスタンスが生成されます。

クラスに__init__()メソッドを定義しておくとインスタンス化のときに引数を__init__()メソッドに渡すことができます。(9.3.2. Class オブジェクト

__init__()メソッドはクラスがインスタンス化された直後に呼び出されるメソッドです。(object.__init__(self[, ...]))

メソッドの第一引数はかならずそのインスタンス自身となります。

なのでクラスのなかで定義している関数(メソッド)の第一引数はかならずselfとなります。(9.4. いろいろな注意点)
    def __init__(self, command, m_args=[]):
インスタンス化のときに渡された引数はこのselfに続きますのでdef fn_dispatch(command, m_args=[]):をクラスの__init__()メソッドに変更するとdef __init__(self, command, m_args=[]):となります。

これで関数fn_dispatch()をクラスFnDispatch__init__()メソッドに変更完了です。

関数dispatch_writer_exp()を呼び出すとクラスFnDispatchのインスタンスがコマンドURLとそのオプション引数を引数として3行目から9行目まで7個も生成されることになります。

♪~♪ d(⌒o⌒)b♪~♪

初めて自分でクラスを作ってちゃんと動いたので嬉しいのですけど、関数fn_dispatch()をクラスFnDispatchにしたメリットはg_exportedScripts = dispatch_writer_exp,を書かなくて済む、ぐらいしか見出せませんね。

インスタンスにオブジェクトをもたせてオブジェクトを使いまわす


UNOオブジェクトのframeとdispatcherは各ディスパッチコマンドに共通なので毎回生成させるのは無駄です。

そこでクラスFnDispatchをインスタンス化したときにframeとdispatcherを生成し、あとはこのインスタンスを使ってディスパッチコマンドを実行することにします。

これによってframeとdispatcherは1回生成させるだけで済みますし、クラスFnDispatchのインスタンスも1個だけ生成させるだけで済みます。
from com.sun.star.beans import PropertyValue
def dispatch_writer_exp():
    disp = FnDispatch() #クラスFnDispatchのインスタンスdispを生成。
    disp.fn_dispatch("InsertText", ["Text", "例文"])
    disp.fn_dispatch("InsertPara")
    disp.fn_dispatch("InsertText", ["Text", "新しい段落に"])
    disp.fn_dispatch("Bold", ["Bold", True])
    disp.fn_dispatch("InsertText", ["Text", "太字の"])
    disp.fn_dispatch("Bold", ["Bold", False])
    disp.fn_dispatch("InsertText", ["Text", "単語を含める"])
class FnDispatch:
    def __init__(self): #インスタンス化された直後に実行されるメソッド。
        self.frame = XSCRIPTCONTEXT.getDesktop().getCurrentFrame() #このインスタンスのデータ属性にframeを追加。
        ctx = XSCRIPTCONTEXT.getComponentContext()
        self.dispatcher = ctx.getServiceManager().createInstanceWithContext("com.sun.star.frame.DispatchHelper",ctx) #このインスタンスのデータ属性にdispatcherを追加。
    def fn_dispatch(self, command, m_args=[]): #インスタンスメソッドfn_dispatchでURLコマンドとオプション引数を引数で受ける。
        if m_args:
            n_args = len(m_args)//2
            args = [PropertyValue() for i in range(n_args)]
            for i in range(n_args):
                args[i].Name = m_args[i*2]
                args[i].Value = m_args[i*2+1]
            args = tuple(args)
        else:
            args = tuple()
        self.dispatcher.executeDispatch(self.frame, ".uno:" + command, "", 0, args)
今度はクラスFnDispatchをインスタンス化しているのは3行目の1回だけです。

そのインスタンスを変数dispに代入しています。

インスタンスdispが生成されるとまず12行目の__init__(self)が実行されます。

selfはそれを呼び出したインスタンス自身なのでインスタンスdispが入ります。

従って以下が実行されることになります。
    def __init__(disp): #インスタンス化された直後に実行されるメソッド。
        disp.frame = XSCRIPTCONTEXT.getDesktop().getCurrentFrame() #このインスタンスのデータ属性にframeを追加。
        ctx = XSCRIPTCONTEXT.getComponentContext()
        disp.dispatcher = ctx.getServiceManager().createInstanceWithContext("com.sun.star.frame.DispatchHelper",ctx) #このインスタンスのデータ属性にdispatcherを追加。
あとはこのdisp.frameとdisp.dispatcherを使い回せばよいわけです。

あとはインスタンスメソッドdisp.fn_dispatch()を引数をいれて呼び出せば16行目以下が実行されることになります。

selfにはインスタンスdispが入ることになるので以下のようになります。
def fn_dispatch(disp, command, m_args=[]): #インスタンスメソッドfn_dispatchでURLコマンドとオプション引数を引数で受ける。
        if m_args:
            n_args = len(m_args)//2
            args = [PropertyValue() for i in range(n_args)]
            for i in range(n_args):
                args[i].Name = m_args[i*2]
                args[i].Value = m_args[i*2+1]
            args = tuple(args)
        else:
            args = tuple()
        disp.dispatcher.executeDispatch(disp.frame, ".uno:" + command, "", 0, args)
disp.frameとdisp.dispatcherはすでにdispを生成したときに代入したものを使うことになります。

オブジェクトの生成回数を減らしたのが功を奏したのか、オートメーションでの実行が明らかに速くなりました。

でもやっぱりLibreOfficeのマクロセレクターから実行する方が明らかに速いです。

汎用化する


どのように汎用化するかはその運用方針によって変わるのですが、私の場合はディスパッチコマンドは、「URLコマンド, [オブション引数のNameとValueの反復]」を反復したリストを引数にして1回のインスタンスメソッドで実行したいと思います。
[
        "InsertText", ["Text", "例文"],
        "InsertPara", [],
        "InsertText", ["Text", "新しい段落に"],
        "Bold", ["Bold", True],
        "InsertText", ["Text", "太字の"],
        "Bold", ["Bold", False],
        "InsertText", ["Text", "単語を含める"],
]
これを引数にします。
from com.sun.star.beans import PropertyValue
class FnDispatch:
    def __init__(self):
        self.frame = XSCRIPTCONTEXT.getDesktop().getCurrentFrame() #フレームの取得。
        ctx = XSCRIPTCONTEXT.getComponentContext() #コンポーネントコンテクストの取得。
        self.dispatcher = ctx.getServiceManager().createInstanceWithContext("com.sun.star.frame.DispatchHelper",ctx) #com.sun.star.frame.DispatchHelperのUNOインスタンスを取得。
    def fn_dispatch(self, args):
        n_args = len(args)//2
        for i in range(n_args):
            command = args[i*2] #URLコマンドを取得。
            m_args = args[i*2+1] #オプション引数のリストを取得。
            n_args1 = len(m_args)//2
            args1 = [PropertyValue()]*n_args1
            for j in range(n_args1):
                args1[j].Name = m_args[j*2]
                args1[j].Value = m_args[j*2+1]
            self.dispatcher.executeDispatch(self.frame, ":".join([".uno", command]), "", 0, tuple(args1))
def dispatch_writer_exp():
    disp = FnDispatch()
    disp.fn_dispatch([
        "InsertText", ["Text", "例文"],
        "InsertPara", [],
        "InsertText", ["Text", "新しい段落に"],
        "Bold", ["Bold", True],
        "InsertText", ["Text", "太字の"],
        "Bold", ["Bold", False],
        "InsertText", ["Text", "単語を含める"],
    ])
クラス定義をインスタンス化の前にしているコードをよくみかけるのでクラス定義を最初にもってきましたが、インスタンス化を先にしても問題ありませんでした。

文字列連結は ''.join(seq) を使うのがベストです。
Python Speed - www.peignot.net
これを知ったので17行目で文字列の連結はjoinを使っています。

13行目はリストの内包表記ではない方法を使ってみました。

参考にしたサイト


Python 標準ライブラリ — Python 3.3.3 ドキュメント
Python3.3.3の解説。

Python チュートリアル — Python 3.3.3 ドキュメント
Python3.3.3のチュートリアル。

キャメルケース - Wikipedia
Pytyonのクラス名は慣習的にアッパーキャメルケースで書きます。

Python Speed - www.peignot.net
Pythonの実行速度を改善するテクニックが書いてあります。

次の関連記事:LibreOffice(38)埋め込みマクロ1:セキュリティレベルを変更して開く

PR

0 件のコメント:

コメントを投稿