LibreOffice5(45)拡張機能内で発生した例外の出力先

拡張機能内でtry文を使っていてエラーが把握できず苦労したので拡張機能内のtry文の扱いについて検討しました。

前の関連記事:LibreOffice5(44)createInstanceWithArgumentsAndContext()でインスタンス化


拡張機能内のprint()は出力されない


拡張機能内のPythonコードはUNOコンポーネント内にあるので外部のPythonコードとはUNOIDL越しでしかやりとりできません。

GitHub - p--q/UNOIDLtype at developで実験します。

PyUnoComponent.pyのexcept節のprint()が出力されていなかったためになかなかエラーがわかりませんでした。

PyUnoComponent.pyの8行目のcomponent.create()の引数を削ってエラーを出してみます。
Traceback (most recent call last):
  File "/home/pq/.config/libreoffice/4/user/Scripts/python/UNOIDLtype/UNOIDLtype/TestPytUnoComponent.py", line 24, in <module>
    s = pycomp.stringTypeArg("文字列を渡しました。")
AttributeError: 'NoneType' object has no attribute 'stringTypeArg'
これでTestPytUnoComponent.pyを実行した場合、このようなエラーがでてきます。

本来は拡張機能内のcomponent.pyで返しているUNOIDLでインスタンス化すべきクラスの__init()__に渡す引数が足りないので、エラーがでている行の一つ前の23行目でPyUNOオブジェクトがNoneで返っているのが原因です。

24行目で指摘されているエラーではPyUNOオブジェクトがインスタンス化されていない原因がわかりません。

except節のprint()が実行されずにtry文が返って害になっているのでtry文自体消してしまいます。
Traceback (most recent call last):
  File "/home/pq/.config/libreoffice/4/user/Scripts/python/UNOIDLtype/UNOIDLtype/TestPytUnoComponent.py", line 23, in <module>
    pycomp = smgr.createInstanceWithContext("UnoInsp", ctx)  # サービス名か実装名でインスタンス化。
uno.com.sun.star.uno.RuntimeException: <class 'TypeError'>: create() missing 1 required positional argument: 'ctx', traceback follows
  /home/pq/.config/libreoffice/4/user/uno_packages/cache/uno_packages/lu262275y7lhm.tmp_/UNOIDLtype.oxt/PyUnoComponent.py:7 in function create() [return component.create(IMPLE_NAME, ctx, *args)]
  /opt/libreoffice5.2/program/unohelper.py:292 in function createInstanceWithContext() [return self.clazz( context )]
今度はちゃんと原因のある部分がエラーで指摘されました。

uno.com.sun.star.uno.RuntimeExceptionが発生して、create()の引数が足りないと言ってくれます。

拡張機能内のファイルを展開したキャッシュのpyファイルのエラーがでている部分までわかります。

/opt/libreoffice5.2/program/msgbox.pyを使ってメッセージボックスに表示させる


print()に代わってCalc(2)課題2:選択範囲の行列番号をメッセージボックスに表示で使ったcreateMessageBox()を使ってメッセージボックスを表示させようと思いましたが、TestPytUnoComponent.pyからはフレームを取得できませんでした。

LibreOfficeのウィンドウを表示させていないので、デスクトップからフレームを取得できないということのようです。

代わりに/opt/libreoffice5.2/program/msgbox.pyにあるMsbBoxクラスを使うとうまくいきました。
def create(ctx, *args):    
    try:
        import component
        return component.create(IMPLE_NAME, ctx, *args)  # この引数を少なくしてエラーがでるようにしている。
    except Exception as e:
        s = str(e)
        from msgbox import MsgBox
        myBox = MsgBox(ctx)
        myBox.addButton("OK")
        myBox.renderFromBoxSize(len(s)*3)  # メッセージボックスの幅を文字列の長さから算出。
        myBox.show(s, 0, "Error")
これでメッセージボックスにExceptionの内容が出力できました。

msgbox.pyを実行すると長い文字列は折り返して表示されますが、どうすると折り返されるのかわからず、文字列ははみ出すので文字列の長さから幅を適当に算出しています。


これがprint(e)で出力されていた内容です。

これよりtry文を削ってスタックトレースの表示をさせた方が得られる情報は多いですね。

createInstanceWithArgumentsAndContext()でインスタンス化している場合


上記のコードミスがあってもTestPytUnoComponent.pyの41行目のcreateInstanceWithArgumentsAndContext()でインスタンス化しているところではこのエラーがでてきません。

createInstanceWithArgumentsAndContext()で渡している引数がcreate()の3つ目の引数として渡されてしまっているからです。

引数の数はあっているのでインスタンス化は成功するのですが、component.py
の8行目のcreate(imple_name, service_name, ctx, *args)のctxにはコンポーネントコンテクストではなくて、createInstanceWithArgumentsAndContext()の引数のタプルが入ってしまいます。

ObjInspクラス内ではコンポーネントコンテクストを使っていないのでエラーはでずに、getInitArgs()メソッドで空のタプルが返ってきてエラーなく終わってしまいます。

でもcreateInstanceWithArgumentsAndContext()でインスタンス化されたオブジェクトをprint()するとterminate called after throwing an instance of 'com::sun::star::uno::RuntimeException'が返ってきてスクリプトが終わります。

print()ではobject.__str__(self)が呼び出されるので、これのエラーですね、おそらく。

どこに原因のコードがあるのかはわかりませんでした。
pyuno object (com.sun.star.uno.XInterface)0x93eea0c{implementationName=UnoInsp, supportedServices={com.blogspot.pq.UnoInsp}, supportedInterfaces={com.sun.star.lang.XServiceInfo,com.blogspot.pq.XUnoInsp,com.sun.star.lang.XTypeProvider}}
エラーのない拡張機能ではpyuno objectの結果が出力されます。

createInstanceWithArgumentsAndContext()の場合はprint()しないとエラーがあることがわからないことになります。

どこかにtry文を仕込んでおけば、エラーのある場所がわかるようにできないか考えてみましたが、わかりませんでした。

この問題はエラーをみてデバッグするのは難しいですね。

思いつくのは引数の型のチェックですが、まあ間違いなくコードすれば問題は発生しないし、一回書けば書き直さないコードなのでこれの対策はしないことにしました。

次の裸の*の文を書いていて解決法がわかりました。

UNOIDLtype/PyUnoComponent.py at 13060c17034890b49eb55ceb402298cd3525569b · p--q/UNOIDLtype

区別する引数をキーワード引数にしてしまえばキーワード引数が足りないのでちゃんとエラーがでるようになりました。

裸の *はそれ以降はキーワード専用引数のみという意味


Python Cookbook9.6. Defining a Decorator That Takes an Optional Argumentの引数の*の意味がわからなかったのですが、偶然これは「キーワード専用引数」とわかりました。

裸の*はつまり*argsのargsのないバージョンということです。

なのでfunc(*args, *, kwarg1, kwarg2)とするとinvalid syntaxというエラーになります。

次の関連記事:LibreOffice5(46)マクロからウェブブラウザに文字列を出力する方法

PR

0 件のコメント:

コメントを投稿