2010-02-14 87 views
22

我目前正在使用Boost.Python爲Python編寫一個C++擴展。此擴展中的函數可能會生成一個異常,其中包含有關錯誤的信息(不僅僅是描述發生事件的人類可讀字符串)。我希望我可以將這個異常導出到Python,這樣我就可以捕獲它,並用額外的信息做一些事情。boost :: 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中捕獲它?

+1

**好問答!**它節省了我的一天!謝謝。 – 2011-11-11 16:41:48

+0

非常好!這裏也很有用!我會5x投票,如果我可以:) – 2016-12-15 11:04:00

回答

25

的解決方案是創建你的異常類像任何普通的C++類

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

訣竅是,所有boost ::蟒蛇:: class_實例在物體的類型的引用是通過他們的PTR訪問()函數。當你與升壓登記類,你可以得到這個::蟒蛇像這樣:

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 
4

Jack Edmonds給出的答案定義了一個Python「異常」類,它不會繼承Exception(或任何其他內置的Python異常類)。所以,雖然它可以與

except my_cpp_extension.MyCPPException as e: 
    ... 

被抓到它不能與一般的抓捕獲所有

except Exception as e: 
    ... 

Here是如何創建一個自定義的Python異常類,確實繼承Exception

+0

但是這不包裹從std :: exception派生的現有C++類...或者我錯過了什麼?如果我不是,你的解決方案並沒有真正回答這個線程中的問題 – 2012-09-06 13:14:59

+0

@Dan Niero:從C++向Python「導出」一個異常的正常方式不是將其封裝,而是將其轉換爲派生的Python異常從'例外'。 – user763305 2012-09-06 14:19:56

+0

我明白你的觀點。但是,如果是引發/拋出異常的C++端,這是在Python中捕獲異常的最佳解決方案? 在這裏的例子中,我可以捕獲從C++代碼拋出的異常。然而,我不能在Python中引發這個異常。我只能抓住它。 如果我沒有錯,在你的解決方案中,你提供了一種方法來從Python提出一個C++異常,但它並沒有讓python「意識到」從C++代碼引發的異常。實際上它是,但它認爲它們都是RuntimeError。 對不起,如果我失去了一些東西,我只是想了解 – 2012-09-06 14:30:57

1

由於可變參數模板和廣義拉姆達捕捉,我們可以摺疊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) 
; 

而現在我們又回到了加速的細微。Python:不需要命名class_實例,不需要額外的PyObject*,並且不需要額外的函數。

+0

我試過你的解決方案,並在Python端得到以下錯誤:'SystemError:exception 不是BaseException子類',並且TypeError:捕獲不能從BaseException繼承的類是不允許的。 Boost.Python V1.61,Python 3.4。 – 2016-10-06 10:05:09