状況
SWIG を使用して C++ API の Python 言語バインディングを作成したいと考えています。一部の API 関数は例外をスローする場合があります。C++ アプリケーションには、次の例のように、自己定義の例外の階層があります。
std::exception
-> API::Exception
-> API::NetworkException
-> API::TimeoutException
-> API::UnreachableException
-> API::InvalidAddressException
望ましい動作は次のとおりです。
すべての例外タイプは、一致する Python クラスをwrapperとして持つ必要があります。これらのラッパー クラスは、有効な Python 例外である必要があります。
API 呼び出しがC++ 例外をスローした場合、それをキャッチする必要があります。対応する Python 例外(つまり、キャッチされた C++ 例外のラッパー クラス) をスローする必要があります。
これは動的なプロセスである必要があります。Python の例外タイプは実行時に決定され、キャッチされた C++ 例外の実行時のタイプのみに基づいて決定されます。この方法では、SWIG インターフェイス ファイルに完全な例外階層を記述する必要はありません。
問題と質問
ラッパー クラスは Python の例外ではありません。
SWIG はすべての自己定義の例外 (他のクラスと同様) に対してラッパー クラスを作成しますが、これらのクラスは Python の例外ではありません。
API::Exception
基本例外 (例)のラッパーは、Python のすべての例外が派生する必要がある Python クラスのObject
代わりに拡張されます。BaseException
さらに、SWIG に手動で親クラスを追加させることはできないようです。これは、Java で SWIG を使用する場合に可能であることに注意して
%typemap(javabase)
ください (詳細については、 SWIG のドキュメントを参照してください)。Python C APIはどのようにしてユーザー定義の例外をスローできますか?
Python C API から Python 例外をスローする最も一般的な方法は、
PyErr_SetString
[reference]を呼び出すことです。これは、以下のデモ アプリケーションにも示されています。しかし、これは Python の標準 (組み込み) 例外の場合は些細なことです。なぜなら、それらへの参照は Python C API のグローバル変数 [参照] に格納されるからです。
自己定義の例外への参照を取得する
PyErr_NewException
[参照]メソッドがあることは知っていますが、これは機能しませんでした。Python C APIはどのようにして実行時に C++ 型を評価し、対応する Python ラッパー クラスを名前で見つけることができますか?
Python C APIのリフレクション部分を介して、実行時に Python クラスを名前で検索できると想定しています。これは行く方法ですか?そして、それは実際にどのように行われますか?
デモ申し込み
この問題を試すために、数値の階乗を計算する関数を 1 つ持つ小さな C++ API を作成しました。これには、1 つのクラスのみで構成される、最小限の自己定義の例外階層がありますTooBigException
。
この例外は一般的な問題の基本例外として機能し、アプリケーションはそのサブクラスで動作する必要があることに注意してください。これは、ソリューションが、キャッチされた例外の動的 (つまりランタイム) タイプのみを使用して、Python で例外を再スローできることを意味します (以下を参照)。
デモ アプリケーションの完全なソース コードは次のとおりです。
// File: numbers.h
namespace numbers {
int fact(int n);
}
// File: numbers.cpp
#include "TooBigException.h"
namespace numbers {
int fact(int n) {
if (n > 10) throw TooBigException("Value too big", n);
else if (n <= 1) return 1;
else return n*fact(n-1);
}
}
// File: TooBigException.h
namespace numbers {
class TooBigException: public std::exception {
public:
explicit TooBigException(const std::string & inMessage,
const int inValue);
virtual ~TooBigException() throw() {}
virtual const char* what() const throw();
const std::string & message() const;
const int value() const;
private:
std::string mMessage;
int mValue;
};
}
// File: TooBigException.cpp
#include "TooBigException.h"
namespace numbers {
TooBigException::TooBigException(const std::string & inMessage, const int inValue):
std::exception(),
mMessage(inMessage),
mValue(inValue)
{
}
const char* TooBigException::what() const throw(){
return mMessage.c_str();
}
const std::string & TooBigException::message() const {
return mMessage;
}
const int TooBigException::value() const {
return mValue;
}
}
Python バインディングを取得するには、次の SWIG インターフェイス ファイルを使用します。
// File: numbers.i
%module numbers
%include "stl.i"
%include "exception.i"
%{
#define SWIG_FILE_WITH_INIT
#include "TooBigException.h"
#include "numbers.h"
%}
%exception {
try {
$action
}
catch (const numbers::TooBigException & e) {
// This catches any self-defined exception in the exception hierarchy,
// because they all derive from this base class.
<TODO>
}
catch (const std::exception & e)
{
SWIG_exception(SWIG_RuntimeError, (std::string("C++ std::exception: ") + e.what()).c_str());
}
catch (...)
{
SWIG_exception(SWIG_UnknownError, "C++ anonymous exception");
}
}
%include "TooBigException.h"
%include "numbers.h"
したがって、API へのすべての呼び出しは、try-catch ブロックによってラップされます。基本型の最初の例外がキャッチされ、処理されます。次に、他のすべての例外がキャッチされ、SWIG 例外ライブラリを使用して再スローされます。
のサブクラスnumbers::TooBigException
がキャッチされ、常にTooBigException
!
Linux マシンで次のコマンドを実行すると、プロジェクトを簡単にビルドできます。
$ swig -c++ -python numbers.i
$ g++ -fPIC -shared TooBigException.cpp numbers.cpp numbers_wrap.cxx \
-I/usr/include/python2.7 -o _numbers.so
現在の実装
私の現在の実装では、まだ (正常に) 固定の標準 Python 例外がスローされます。上記のコード<TODO>
は次のように置き換えられます。
PyErr_SetString(PyExc_Exception, (std::string("C++ self-defined exception ") + e.what()).c_str());
return NULL;
これにより、Python で次の (予想される) 動作が得られます。
>>> import numbers
>>> fact(11)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception: C++ self-defined exception Value too big