LibreOffice5(49)Javaの例:ConfigExamplesをPythonにする その2

Python Cookbook08.15.delegation_attribute_accessに載っているProxyパターンと01.18.mapping_names_to_sequence_elementsに載っているnamedtupleを使いました。

前の関連記事:LibreOffice5(48)Javaの例:ConfigExamplesをPythonにする その1


ConfigurationAccessサービスのインスタンスにProxyでメソッドを追加する

    visible = root.getNode("Option/VisibleGrid")
    resolution_x, resolution_y = root.getNode("Resolution").getNode("XAxis/Metric", "YAxis/Metric")
    subdivision_x, subdivision_y = root.getNode("Subdivision").getNode("XAxis", "YAxis")
/org.openoffice.Office.Calc/Gridをrootとしてそのサブノードを一つのgetNode()というメソッドでメソッドチェーンで取得できるようにしたいと考えました。

ConfigurationAccessサービスのgetHierarchicalPropertyValue()、getHierarchicalPropertyValues()、getPropertyValue()、getPropertyValues()の4つのメソッドをgetNode()という一つのメソッドで処理することになります。

サービスがクラスとして扱えて継承できれば、getNode()をメソッドの一つに加えて、インスタンスを返すようにすればよいかな、と考えましたが、サービスをクラスとして扱う方法がそもそもわからないので、サブクラスを作る方法については追及しませんでした。

で、Python Cookbookをめくって見つけたのが08.15.delegation_attribute_accessに載っているProxyパターンを使う方法です。

example2.pyのクラスProxyを使うとメソッドを追加したインスタンスを引数にしてProxyクラスをインスタンス化するだけで、Proxyクラスに追加したメソッドが引数のインスタンスに追加できます。

linuxBean14.04(170)Pythonのデスクリプタの学習を少しで使ったtypes.MethodType()でもインスタンスにメソッドを追加できますが、メソッドチェーンにするためにはオブジェクトを返す方法を別に考えないといけないので面倒です。
class Proxy:
    def __init__(self, obj):
        self._obj = obj
    def getNode(self, *args):
        delimset = {"/", ".", ":"}
        if len(args)==1:
            node = self._obj.getHierarchicalPropertyValue(*args) if delimset & set(*args) else self._obj.getPropertyValue(*args)
            return Proxy(node) if hasattr(node, "getTypes") else node
        elif len(args)>1:
            nodes = self._obj.getHierarchicalPropertyValues(args) if delimset & set("".join(args)) else self._obj.getPropertyValues(args)
            return [Proxy(node) if hasattr(node, "getTypes") else node for node in nodes]
    def __getattr__(self, name):
        return getattr(self._obj, name)
    def __setattr__(self, name, value):
        super().__setattr__(name, value) if name.startswith('_') else setattr(self._obj, name, value)
    def __delattr__(self, name):
        super().__delattr__(name) if name.startswith('_') else delattr(self._obj, name)   
example2.pyのクラスProxyにgetNode()メソッドを追加しました。

getNode()では引数の数が一つか複数かで場合分けしたあとさらに引数の文字列の中にスラッシュかドット、コロンのいずれかの文字があるかで場合分けして4つのメソッドに振り分けています。

戻り値の属性にgetTypesがあればPyUNOオブジェクトとしてまたProxyの引数にしてProxyオブジェクトを返し、そうでなければ値としてそのまま返しています。

Javaの例ではcom.sun.star.uno.AnyConverterのisObjectメソッドを使ってPyUNOオブジェクトかどうか調べています。

このisObjectメソッドではinterface, struct, exception, sequence, enumであるか調べてTrueを返しています。

Pythonで利用できる同じ働きをする関数を探してみましたが見つけられませんでした。

なので、getTypesがあるかどうかでPyUNOオブジェクトかどうかを判断しています。
(2017.6.18追記。PyUNOオブジェクトをtype()にかけると<class 'pyuno'>が返ってくるのでtype(object).__name__=="pyuno"で判定することにしました。)

あとはConfigurationAccessサービスのインスタンスをこのProxyクラスの引数にしてインスタンス化すればgetNode()メソッドが使えるようになります。
def readGridConfiguration(cp):
    ca = createConfigurationView("/org.openoffice.Office.Calc/Grid", cp)
    root = Proxy(ca)
    visible = root.getNode("Option/VisibleGrid")
    resolution_x, resolution_y = root.getNode("Resolution").getNode("XAxis/Metric", "YAxis/Metric")
    subdivision_x, subdivision_y = root.getNode("Subdivision").getNode("XAxis", "YAxis")
    ca.dispose()
    return GridOptions(visible, resolution_x, resolution_y, subdivision_x, subdivision_y) 
これでConfigurationAccessサービスのインスタンスをメソッドチェーンを使ってノードを追えるようになりました。

プロパティに代わってcollections.namedtuple()を使う


readGridConfiguration()はGridOptionのインスタンスを返しています。
class GridOptions(namedtuple("GridOptions", "visible resolution_x resolution_y subdivision_x subdivision_y")):
    __slots__ = ()
    def __str__(self):
        return "[ Grid is {0}; resolution = ({1},{2}); subdivision = ({3},{4}) ]"\
            .format("VISIBLE" if self.visible else "HIDDEN", self.resolution_x, self.resolution_y, self.subdivision_x, self.subdivision_y)
これはnamedtupleを継承して__str__メソッドを追加したものでPythonの標準ライブラリのドキュメントに載っている例をそのまま使っています。

namedtuple自体はPython Cookbook01.18.mapping_names_to_sequence_elementsを読んで知りました。

linuxBean14.04(170)Pythonのデスクリプタの学習を少しで学習したプロパティを使うとオーバーヘッドが大きくなるのですが、namedtupleを使うとタプルより少しオーバーヘッドが増えるだけだそうです。

__slots__はPythonの標準ライブラリのドキュメントの例にあるままに使っています。

Python Cookbook01.18.mapping_names_to_sequence_elementsの解説の最後にも__slots__を使うと効率的なデータ構造を構築できると書いてあり、08.04Saving Memory When Creating a Large Number of Instancesに__slots__について解説がありますが、何百万ぐらいのインスタンスを作成するときに役立つもののようです。

ConfigExamples/configexamples.py at 0c8e666083c27de6b988eb1d627a64273049f919 · p--q/ConfigExamples

readDataExample()はこれで翻訳完了です。

参考にしたサイト


AnyConverter (Java UNO Runtime Reference)
JavaでUNOタイプの判定や変換をするクラス。

8.3. collections — コンテナデータ型 — Python 3.6.1 ドキュメント
namedtupleの解説。

3. データモデル — Python 3.6.1 ドキュメント
__slots__の解説。

次の関連記事:LibreOffice5(50)Javaの例:ConfigExamplesをPythonにする その3

PR

0 件のコメント:

コメントを投稿