boost::python Export Custom Exception

后端 未结 4 1207
天涯浪人
天涯浪人 2020-12-13 03:24

I am currently writing a C++ extension for Python using Boost.Python. A function in this extension may generate an exception containing information about the error (beyond

4条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2020-12-13 03:47

    Here's the solution from Jack Edmonds, ported to Python 3, using advice from here which itself uses code from here. Assembling it all together (and modernizing the C++ code a little bit) gives:

    #include 
    #include 
    #include 
    
    class MyCPPException : public std::exception
    {
    public:
        MyCPPException(const std::string &message, const std::string &extraData)
            : message(message), extraData(extraData)
        {
        }
        const char *what() const noexcept override
        {
            return message.c_str();
        }
        std::string getMessage() const
        {
            return message;
        }
        std::string getExtraData() const
        {
            return extraData;
        }
    private:
        std::string message;
        std::string extraData;
    };
    
    void my_cpp_function(bool throwException)
    {
        std::cout << "Called a C++ function." << std::endl;
        if (throwException) {
            throw MyCPPException("Throwing an exception as requested.",
                                 "This is the extra data.");
        }
    }
    
    static PyObject* createExceptionClass(const char* name, PyObject* baseTypeObj = PyExc_Exception)
    {
        using std::string;
        namespace bp = boost::python;
    
        const string scopeName = bp::extract(bp::scope().attr("__name__"));
        const string qualifiedName0 = scopeName + "." + name;
        PyObject* typeObj = PyErr_NewException(qualifiedName0.c_str(), baseTypeObj, 0);
        if (!typeObj) bp::throw_error_already_set();
        bp::scope().attr(name) = bp::handle<>(bp::borrowed(typeObj));
        return typeObj;
    }
    
    static PyObject *pythonExceptionType = NULL;
    
    static void translateMyCPPException(MyCPPException const &e)
    {
        using namespace boost;
        python::object exc_t(python::handle<>(python::borrowed(pythonExceptionType)));
        exc_t.attr("cause") = python::object(e); // add the wrapped exception to the Python exception
        exc_t.attr("what") = python::object(e.what()); // for convenience
        PyErr_SetString(pythonExceptionType, e.what()); // the string is used by print(exception) in python
    }
    
    BOOST_PYTHON_MODULE(my_cpp_extension)
    {
        using namespace boost;
        python::class_
                myCPPExceptionClass("MyCPPException",
                                    python::init());
        myCPPExceptionClass.add_property("message", &MyCPPException::getMessage)
                .add_property("extra_data", &MyCPPException::getExtraData);
    
        pythonExceptionType = createExceptionClass("MyPythonException");
        python::register_exception_translator(&translateMyCPPException);
        python::def("my_cpp_function", &my_cpp_function);
    }
    

    and the python file to test it:

    #!/usr/bin/env python3
    
    import my_cpp_extension
    try:
        my_cpp_extension.my_cpp_function(False)
        print('This line should be reached as no exception should be thrown.')
    except my_cpp_extension.MyPythonException as e:
        print('Message:', e.what)
        print('Extra data:',e.cause.extra_data)
    
    try:
        my_cpp_extension.my_cpp_function(True)
        print ('This line should not be reached as an exception should have been' +
           'thrown by now.')
    except my_cpp_extension.MyPythonException as e:
        print('Message:', e.what)
        print('Extra data:',e.cause.extra_data)
    

    And catching it as a standard python Exception works too:

    except Exception as e:
        print('Exception: ',e)
    

提交回复
热议问题