【问题标题】:boost::python Export Custom Exceptionboost::python 导出自定义异常
【发布时间】:2011-01-16 17:44:44
【问题描述】:

我目前正在使用 Boost.Python 为 Python 编写 C++ 扩展。此扩展中的函数可能会生成包含有关错误信息的异常(不仅仅是描述所发生情况的人类可读字符串)。我希望我可以将这个异常导出到 Python,这样我就可以捕获它并使用额外的信息做一些事情。

例如:

import my_cpp_module
try:
    my_cpp_module.my_cpp_function()
except my_cpp_module.MyCPPException, e:
    print e.my_extra_data

不幸的是,Boost.Python 似乎将所有 C++ 异常(它们是 std::exception 的子类)转换为 RuntimeError。我意识到 Boost.Python 允许实现自定义异常翻译,但是需要使用 PyErr_SetObject ,它需要一个 PyObject* (用于异常的类型)和一个 PyObject* (用于异常的值)--两者都不是我知道如何从我的 Boost.Python 类中获取信息。也许有一种我还没有找到的方法(这会很棒)。否则有谁知道如何导出自定义 C++ 异常以便我可以在 Python 中捕获它?

【问题讨论】:

    标签: c++ python exception boost-python


    【解决方案1】:

    解决方案是像创建任何普通 C++ 类一样创建异常类

    class MyCPPException : public std::exception {...}
    

    诀窍是所有 boost::python::class_ 实例都持有对对象类型的引用,该引用可通过它们的 ptr() 函数访问。你可以在使用 boost::python 注册类时得到它,如下所示:

    class_<MyCPPException> myCPPExceptionClass("MyCPPException"...);
    PyObject *myCPPExceptionType=myCPPExceptionClass.ptr();
    register_exception_translator<MyCPPException>(&translateFunc);
    

    最后,当您将 C++ 异常转换为 Python 异常时,请执行以下操作:

    void translate(MyCPPException const &e)
    {
        PyErr_SetObject(myCPPExceptionType, boost::python::object(e).ptr());
    }
    

    这是一个完整的工作示例:

    #include <boost/python.hpp>
    #include <assert.h>
    #include <iostream>
    
    class MyCPPException : public std::exception
    {
    private:
      std::string message;
      std::string extraData;
    public:
      MyCPPException(std::string message, std::string extraData)
      {
        this->message = message;
        this->extraData = extraData;
      }
      const char *what() const throw()
      {
        return this->message.c_str();
      }
      ~MyCPPException() throw()
      {
      }
      std::string getMessage()
      {
        return this->message;
      }
      std::string getExtraData()
      {
        return this->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.");
        }
    }
    
    PyObject *myCPPExceptionType = NULL;
    
    void translateMyCPPException(MyCPPException const &e)
    {
      assert(myCPPExceptionType != NULL);
      boost::python::object pythonExceptionInstance(e);
      PyErr_SetObject(myCPPExceptionType, pythonExceptionInstance.ptr());
    }
    
    BOOST_PYTHON_MODULE(my_cpp_extension)
    {
      boost::python::class_<MyCPPException>
        myCPPExceptionClass("MyCPPException",
                boost::python::init<std::string, std::string>());
      myCPPExceptionClass.add_property("message", &MyCPPException::getMessage)
        .add_property("extra_data", &MyCPPException::getExtraData);
      myCPPExceptionType = myCPPExceptionClass.ptr();
      boost::python::register_exception_translator<MyCPPException>
        (&translateMyCPPException);
      boost::python::def("my_cpp_function", &my_cpp_function);
    }
    

    这是调用扩展的 Python 代码:

    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.MyCPPException, e:
        print 'Message:', e.message
        print 'Extra data:',e.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.MyCPPException, e:
        print 'Message:', e.message
        print 'Extra data:',e.extra_data
    

    【讨论】:

    • 很好的答案,但它适用于 Python 2。将 python 代码移植到 python 3 后,这会在运行时发生:SystemError: exception &lt;class 'my_cpp_extension.MyCPPException'&gt; not a BaseException subclass
    • 查看我对此代码的 python 3 版本的回答。
    【解决方案2】:

    Jack Edmonds 给出的答案定义了一个不继承 Exception(或任何其他内置 Python 异常类)的 Python“异常”类。所以虽然可以被抓到

    except my_cpp_extension.MyCPPException as e:
        ...
    

    不能用通常的全部捕获

    except Exception as e:
        ...
    

    Here 是如何创建自定义 Python 异常类 继承 Exception

    【讨论】:

    • 但这并没有包装从 std::exception 派生的现有 c++ 类......还是我遗漏了什么?如果我不是,你的解决方案并没有真正回答这个线程中的问题
    • @Dan Niero:将异常从 C++“导出”到 Python 的正常方法不是包装它,而是将其转换为派生自 Exception 的 Python 异常。
    • 我明白你的意思。但是,如果是 c++ 端引发/抛出异常,那么在 Python 中捕获该异常的最佳解决方案是什么?在此处的示例中,我可以捕获从 c++ 代码引发的异常。但是,我不能从 python 中引发该异常。我只能抓住它。如果我没记错的话,在您的解决方案中,您提供了一种从 python 引发 c++ 异常的方法,但它不会让 python“意识到”从 c++ 代码引发的异常。实际上是,但它认为它们都是RuntimeError。对不起,如果我遗漏了什么,我只是想理解
    • @Dan Niero:您不能真正在 C++ 端抛出异常并在 Python 端捕获它。您必须在 C++ 端捕获异常,然后调用 PyErr_SetStringPyErr_SetObject 来引发 Python 异常。如果您使用的是 Boost.Python,那么 Boost.Python 会自动为您执行此操作。如果 Boost.Python 无法识别 C++ 异常,则默认情况下会引发 RuntimeError。但是,您可以通过安装自己的异常转换器来覆盖该默认值。在那里,您可以将自己的 C++ 异常转换为自己的 Python 异常。
    【解决方案3】:

    多亏了可变参数模板和通用的 lambda 捕获,我们可以将 Jack Edmond's answer 折叠成更易于管理的东西,并对用户隐藏所有琐碎的东西:

    template <class E, class... Policies, class... Args>
    py::class_<E, Policies...> exception_(Args&&... args) {
        py::class_<E, Policies...> cls(std::forward<Args>(args)...);
        py::register_exception_translator<E>([ptr=cls.ptr()](E const& e){
            PyErr_SetObject(ptr, py::object(e).ptr());
        });
        return cls;
    }
    

    要将MyCPPException 暴露为异常,只需将绑定中的py::class_ 更改为exception_

    exception_<MyCPPException>("MyCPPException", py::init<std::string, std::string>())
        .add_property("message", &MyCPPException::getMessage)
        .add_property("extra_data", &MyCPPException::getExtraData)
    ;
    

    现在我们回到 Boost.Python 的精妙之处:不需要命名 class_ 实例,不需要这个额外的 PyObject*,也不需要在某个地方添加额外的函数。

    【讨论】:

    • 我尝试了您的解决方案,但在 Python 端出现以下错误:SystemError: exception &lt;class 'MyCPPException'&gt; not a BaseException subclassTypeError: catching classes that do not inherit from BaseException is not allowed。 Boost.Python V1.61,Python 3.4。
    • 同样尝试了这个解决方案并得到TypeError: exceptions must derive from BaseException :(
    【解决方案4】:

    这是 Jack Edmonds 的解决方案,它使用来自 here 的建议移植到 Python 3,它本身使用来自 here 的代码。将它们组装在一起(并对 C++ 代码进行一点现代化改造)可以得到:

    #include <boost/python.hpp>
    #include <assert.h>
    #include <iostream>
    
    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<string>(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_<MyCPPException>
                myCPPExceptionClass("MyCPPException",
                                    python::init<std::string, std::string>());
        myCPPExceptionClass.add_property("message", &MyCPPException::getMessage)
                .add_property("extra_data", &MyCPPException::getExtraData);
    
        pythonExceptionType = createExceptionClass("MyPythonException");
        python::register_exception_translator<MyCPPException>(&translateMyCPPException);
        python::def("my_cpp_function", &my_cpp_function);
    }
    

    以及测试它的python文件:

    #!/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)
    

    并且将其作为标准 python 异常捕获也可以:

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

    【讨论】:

      【解决方案5】:

      你在 macOS 上测试过吗?该解决方案在 Linux (gcc) 和 Windows (VS) 中完美运行,但是当我在 macOS Big Sur (Xcode Clang) 中对其进行测试时,我得到以下错误而不是异常:

      Called a C++ function.
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      SystemError: <Boost.Python.function object at 0x7fdcf5c30700> returned NULL without setting an error
      

      【讨论】:

      • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
      【解决方案6】:

      我结合了 Barry 和 David Faure 的答案并创建了一个有效的 python 异常。 它提取异常名称的 S 参数(因此它必须显式传递给 class_ 对象)。

      template <class E, class... Policies, class S, class... Args>
      boost::python::class_<E, Policies...> exception_(S name, Args&&... args) {
          boost::python::class_<E, Policies...> cls(name, std::forward<Args>(args)...);
              
          pythonExceptionType = createExceptionClass(name);
          
          boost::python::register_exception_translator<E>([ptr=pythonExceptionType](E const& e){
              boost::python::object exc_t(boost::python::handle<>(boost::python::borrowed(ptr)));
              exc_t.attr("cause") = boost::python::object(e); 
              exc_t.attr("what") = boost::python::object(e.what());
              PyErr_SetString(ptr, e.what());
              PyErr_SetObject(ptr, boost::python::object(e).ptr());
          });
          return cls;
      }
      
      static PyObject* createExceptionClass(const char* name, PyObject* baseTypeObj = PyExc_Exception)
      {
          using std::string;
          namespace bp = boost::python;
      
          const string scopeName = bp::extract<string>(bp::scope().attr("__name__"));
          const string qualifiedName0 = scopeName + "." + name;
          PyObject* typeObj = PyErr_NewException(qualifiedName0.c_str(), baseTypeObj, 0);
          bp::scope().attr(name) = bp::handle<>(bp::borrowed(typeObj));
          return typeObj;
      }
      
      BOOST_PYTHON_MODULE(MyModule)
      
      exception_<MyException, bases<SomeBaseException>>("MyException", no_init)
                  .def("get_message", &MyException::get_message)
                  .def("get_reason", &MyException::get_reason)
                  ;
      

      在python中

      try:
          do_sth()
      except MyModule.MyException as e: 
          print(e.cause.get_message())
      
          print(e.cause.get_reason())
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-06
        • 2021-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多