LoginSignup
1

More than 3 years have passed since last update.

boostpythonでC++の例外をpythonに公開する

Posted at

はじめに

一般的にC系の言語でエラーが発生したときはステータスコードを戻り値として使用するのが多いようですが、
Pythonを使用しているとエラーしたときはエクセプションを発生させたい。
そのため、C++でエラーが発生した時のためにC++の例外をpythonに公開してみます。

環境

  • Ubuntu:18.04.2
  • python3:3.6.8

環境作成については、ubuntuでboost pythonを使用するに載せています。

C++の標準的なエクセプションの公開

C++の標準的なエクセプションを公開するときは、特別なことをしないでもboostpythonがpythonへ公開してくれます。

実例

1.公開する関数としてエクセプションを発生する関数を作成します。

CppMod.cpp
   void execExcept() {
       throw std::runtime_error("normal except");
   }

2.python側に公開するための設定をします。

CppMod.cpp
   BOOST_PYTHON_MODULE(CppMod) {
       boost::python::def("execExcept", &execExcept);
   }

3.python側で呼び出すと1で指定したnormal exceptが表示されます。

   >>> import CppMod
   >>> CppMod.execExcept()
   Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
   RuntimeError: normal except

C++の独自エクセプションの公開

表現したいエラーが標準のエクセプションでは網羅できないため、独自のエクセプションを発生させたいことが往々にしてありえます。
その際は、C++のエクセプションを拡張して、変換をboostpythonに登録すると実現できます。

ポイント

class 独自エクセプション : std::exception {
...
};
void translateMyExcept(独自エクセプション e) {
    PyErr_SetString(python側へ伝えるエクセプション種類, e.what() );
}
BOOST_PYTHON_MODULE(pythonへ公開するモジュール名) {
    boost::python::register_exception_translator<独自エクセプション>(&translateMyExcept);

実例

1.C++のエクセプションを拡張したクラスを作成します。

CppMod.cpp
   class my_exception : std::exception {
       public:
       char const* my_message = "";

       my_exception(char const* message) {
          my_message = message;
       }
       char const* what() { 
           return my_message;
       }
   };

2.独自エクセプションの変換用関数を作成します。

CppMod.cpp
   void translateMyExcept(my_exception e) {
       PyErr_SetString(PyExc_RuntimeError, e.what() );
   }

3.変換用関数をboostpythonに登録します。

CppMod.cpp
   BOOST_PYTHON_MODULE(CppMod) {
       boost::python::register_exception_translator<my_exception>(&translateMyExcept);
        boost::python::def("execExcept", &execExcept);
    }

4.実験用に独自エクセプションを発生させるだけの関数を作成する。

CppMod.cpp
   void execExcept() {
       throw my_exception("custom except");
   }

5.pythonを実行する。

   >>> import CppMod
   >>> CppMod.execExcept()
   Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
   RuntimeError: custom except    

C++の独自エクセプションの拡張

今までは、C++側でエラーが発生した際にpython側へ標準エクセプションの種別で1つのメッセージしか遅れていませんでした。※上の例ならエクセプション種別はRuntimeError、メッセージはcustom except
さらに、エクセプションを拡張してエラーコードを持たせたり、python側のexcept構文の分岐に引っかかるようにします。

ポイント

class 独自エクセプション名 : std::exception {
...
};
PyObject* createExceptionClass() {
    char* myExceptPath = const_cast<char*>(モジュール名.エクセプション名);
    PyObject* myExceptObj = PyErr_NewException(myExceptPath, PyExc_Exception, NULL);
    boost::python::scope().attr(エクセプション名) = boost::python::handle<>(boost::python::borrowed(myExceptObj));
    return myExceptObj;
}
PyObject* myExceptionTypeObj = 0;

void translateMyExcept(my_exception e) {
    PyObject_SetAttrString(myExceptionTypeObj,"error_code", PyLong_FromLong(e.my_code));  
    PyErr_SetString(myExceptionTypeObj, e.what());
}
BOOST_PYTHON_MODULE(CppMod) {
    myExceptionTypeObj = createExceptionClass();
    boost::python::register_exception_translator<my_exception>(&translateMyExcept);

実例

1.C++のエクセプションを拡張したクラスを作成します。

CppMod.cpp
   class my_exception : std::exception {
       public:
       char const* my_message = "";
       int my_code = 0;

       my_exception(char const* message, int code) {
           my_message = message;
           my_code = code;
       }
       char const* what() { 
           return my_message;
       }
   };

2.独自エクセプションの変換用関数を作成します。

CppMod.cpp
   PyObject* myExceptionTypeObj = 0;
    void translateMyExcept(my_exception e) {
       PyObject_SetAttrString(myExceptionTypeObj,"error_code", PyLong_FromLong(e.my_code));  
       PyErr_SetString(myExceptionTypeObj, e.what());
   }

3.python側で使用するエクセプションの設定をします。

CppMod.cpp
   PyObject* createException() {
       char* myExceptPath = const_cast<char*>("CppMod.MyException");
       PyObject* myExceptObj = PyErr_NewException(myExceptPath, PyExc_Exception, NULL);
       boost::python::scope().attr("MyException") = boost::python::handle<>(boost::python::borrowed(myExceptObj));
       return myExceptObj;
   }

4.変換用関数をboostpythonに登録します。

CppMod.cpp
   BOOST_PYTHON_MODULE(CppMod) {
       myExceptionTypeObj = createException();
       boost::python::register_exception_translator<my_exception>(&translateMyExcept);
       boost::python::def("execExcept", &execExcept);
   }

5.実験用に独自エクセプションを発生させるだけの関数を作成する。

CppMod.cpp
   void execExcept() {
       throw my_exception("custom except");
   }

6.実験用にpythonファイルを作成する。

pyMod.py
   import traceback
   import CppMod

   try:
       num = CppMod.execExcept()
   except CppMod.MyException as e:
       print("---- error code ----")
       print(e.error_code)
       print("---- trace ----")
       print(traceback.format_exc())

7.実行する

   # python3 pyMod.py
   ---- error code ----
   1
   ---- trace ----
   Traceback (most recent call last):
   File "pyMod.py", line 6, in <module>
       num = CppMod.execExcept()
   CppMod.MyException: custom except

おわりに

boostpythonというより、phthon/C APIの機能をふんだんに使った説明になってしまいました。
そのため、もしかするとpython2では使えない可能性が若干あります。
最後の独自エクセプションを拡張するのは、いろいろな人がいろいろな方法で実現しているので
調べるのにすごく苦労しました。
次回はコンバーターとポリシー周りのことを書こうかなと思っていますが、挫折するかも。。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1