独自の C++ 例外タイプを定義するカスタム C++ ライブラリの SWIG ラッパーを作成しています。ライブラリの例外の種類は、標準の例外よりも豊富で具体的です。(たとえば、1 つのクラスが解析エラーを表し、行番号のコレクションを持っています。) 例外の型を保持しながら、これらの例外を Python に伝播するにはどうすればよいですか?
6 に答える
この質問が数週間前のものであることは知っていますが、自分で解決策を研究していたときに見つけました. だから私は答えを突き刺しますが、swigインターフェースファイルはラッパーを手作業でコーディングするよりも複雑になる可能性があるため、魅力的なソリューションではない可能性があることを事前に警告します. また、私が知る限り、swig のドキュメントはユーザー定義の例外を直接扱っていません。
C++ コード モジュール mylibrary.cpp から次の例外をスローし、Python コードでキャッチされる小さなエラー メッセージをスローするとします。
throw MyException("Highly irregular condition..."); /* C++ code */
MyException は、別の場所で定義されたユーザー例外です。
始める前に、Python でこの例外をキャッチする方法をメモしておいてください。ここでの目的のために、次のような Python コードがあるとします。
import mylibrary
try:
s = mylibrary.do_something('foobar')
except mylibrary.MyException, e:
print(e)
これはあなたの問題を説明していると思います。
私の解決策は、次のように swig インターフェイス ファイル (mylibrary.i) に 4 つの追加を行うことです。
ステップ 1: ヘッダー ディレクティブ (通常は名前のない %{...%} ブロック) で、Python 対応の例外へのポインターの宣言を追加します。これを pMyException と呼びます。以下のステップ 2 でこれを定義します。
%{
#define SWIG_FILE_WITH_INIT /* for eg */
extern char* do_something(char*); /* or #include "mylibrary.h" etc */
static PyObject* pMyException; /* add this! */
%}
ステップ 2: 初期化ディレクティブを追加します (「m」は特に悪質ですが、swig v1.3.40 が構築されたラッパー ファイルのその時点で現在必要としているものです) - pMyException は上記のステップ 1 で宣言されています。
%init %{
pMyException = PyErr_NewException("_mylibrary.MyException", NULL, NULL);
Py_INCREF(pMyException);
PyModule_AddObject(m, "MyException", pMyException);
%}
ステップ 3: 以前の投稿で述べたように、例外ディレクティブが必要です。「%except(python)」は非推奨であることに注意してください。これにより、c+++ 関数 "do_something" が try-except ブロックにラップされます。このブロックは、c++ 例外をキャッチし、上記の手順 2 で定義された Python 例外に変換します。
%exception do_something {
try {
$action
} catch (MyException &e) {
PyErr_SetString(pMyException, const_cast<char*>(e.what()));
SWIG_fail;
}
}
/* The usual functions to be wrapped are listed here: */
extern char* do_something(char*);
ステップ 4: swig は .pyd dll の周囲に Python ラッピング (「シャドウ」モジュール) を設定するため、Python コードが .pyd ファイルを「透視」できることも確認する必要があります。以下は私にとってはうまくいきました.swig pyラッパーコードを直接編集するよりも望ましいです:
%pythoncode %{
MyException = _mylibrary.MyException
%}
元のポスターにあまり役立つにはおそらく遅すぎますが、おそらく他の誰かが上記の提案を何らかの用途に見つけるでしょう. 小さなジョブの場合、swig インターフェイス ファイルの混乱よりも、手作業でコーディングされた C++ 拡張ラッパーのクリーンさを好む場合があります。
追加した:
上記のインターフェイス ファイルのリストでは、標準モジュール ディレクティブを省略しました。ただし、モジュール行は次のようになります。
%module mylibrary
また、setup.py (distutils を使用している場合は、少なくとも開始することをお勧めします) には次のようなコードが含まれている必要があります。そうしないと、_mylibrary が認識されない場合にステップ 4 が失敗します。
/* setup.py: */
from distutils.core import setup, Extension
mylibrary_module = Extension('_mylibrary', extra_compile_args = ['/EHsc'],
sources=['mylibrary_wrap.cpp', 'mylibrary.cpp'],)
setup(name="mylibrary",
version="1.0",
description='Testing user defined exceptions...',
ext_modules=[mylibrary_module],
py_modules = ["mylibrary"],)
Windows で例外をコンパイルするために必要なコンパイル フラグ /EHsc に注意してください。プラットフォームでそのフラグが必要ない場合があります。グーグルに詳細があります。
ソリューションを一般化するのに役立つように、ここで「mylibrary」および「MyException」に変換した独自の名前を使用してコードをテストしました。うまくいけば、転記エラーはほとんどまたはまったくありません。主なポイントは、 %init および %pythoncode ディレクティブと、
static PyObject* pMyException;
ヘッダー ディレクティブで。
解決策を明確にすることを願っています。
ここに示されている例では、「%except(python)」は非推奨であることが示されているため、ここに少し追加します...
(とにかく swig 1.3.40 の時点で) 完全に一般的な、スクリプト言語に依存しない翻訳を行うことができるようになりました。私の例は次のとおりです。
%exception {
try {
$action
} catch (myException &e) {
std::string s("myModule error: "), s2(e.what());
s = s + s2;
SWIG_exception(SWIG_RuntimeError, s.c_str());
} catch (myOtherException &e) {
std::string s("otherModule error: "), s2(e.what());
s = s + s2;
SWIG_exception(SWIG_RuntimeError, s.c_str());
} catch (...) {
SWIG_exception(SWIG_RuntimeError, "unknown exception");
}
}
これにより、Python を含むサポートされているスクリプト言語で RuntimeError 例外が生成されますが、他のヘッダーに Python 固有のものは含まれません。
この例外処理が必要な呼び出しの前にこれを配置する必要があります。
swigのドキュメントから
%except(python) {
try {
$function
}
catch (RangeError) {
PyErr_SetString(PyExc_IndexError,"index out-of-bounds");
return NULL;
}
}
swig例外のドキュメントは役に立ちますか? さまざまな例外ハンドラーの定義について言及しています..