2010-02-14 6 views
22

Tôi hiện đang viết một phần mở rộng C++ cho Python bằng cách sử dụng Boost.Python. Một hàm trong phần mở rộng này có thể tạo ra một ngoại lệ chứa thông tin về lỗi (ngoài chỉ là một chuỗi có thể đọc được của con người mô tả những gì đã xảy ra). Tôi đã hy vọng tôi có thể xuất ngoại lệ này sang Python để tôi có thể bắt nó và làm điều gì đó với thông tin bổ sung.boost :: python Xuất ngoại lệ tùy chỉnh

Ví dụ:

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

Thật không may Boost.Python dường như tất cả các dịch C++ ngoại lệ (đó là lớp con của std::exception) vào RuntimeError. Tôi nhận ra rằng Boost.Python cho phép một để thực hiện dịch ngoại lệ tùy chỉnh tuy nhiên, một trong những nhu cầu sử dụng PyErr_SetObject mà mất một PyObject* (đối với loại ngoại lệ) và PyObject* (cho giá trị của ngoại lệ) - không phải trong số đó tôi biết làm thế nào để có được từ các lớp Boost.Python của tôi. Có lẽ có một cách (mà sẽ là tuyệt vời) mà tôi chỉ đơn giản là đã không tìm thấy được nêu ra. Nếu không có ai biết làm thế nào để xuất khẩu một ngoại lệ C++ tùy chỉnh để tôi có thể bắt nó trong Python?

+1

** Câu hỏi và trả lời hay! ** Đã lưu ngày của tôi! Cảm ơn bạn. –

+0

Tuyệt vời! rất hữu ích ở đây quá! Tôi sẽ 5x bỏ phiếu bầu nếu tôi có thể :) –

Trả lời

25

Giải pháp là để tạo ra lớp ngoại trừ của bạn giống như bất kỳ C bình thường ++ lớp

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

Bí quyết là rằng tất cả tăng :: trường python :: class_ giữ một tham chiếu đến kiểu của đối tượng có thể truy cập thông qua ptr của họ() chức năng. Bạn có thể có được điều này khi bạn đăng ký các lớp học với boost :: python như vậy:

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

Cuối cùng, khi bạn đang dịch cáC++ ngoại lệ C đến một ngoại lệ Python, bạn làm như vậy như sau:

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

Dưới đây là một ví dụ làm việc đầy đủ:

#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); 
} 

đây là mã Python mà các cuộc gọi mở rộng:

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

Câu trả lời được đưa ra bởi Jack Edmonds định nghĩa một lớp "ngoại lệ" Python không kế thừa Exception (hoặc bất kỳ lớp ngoại lệ Python được xây dựng khác). Vì vậy, mặc dù nó có thể được bắt gặp với

except my_cpp_extension.MyCPPException as e: 
    ... 

nó không thể được đánh bắt với đánh bắt thông thường tất cả

except Exception as e: 
    ... 

Here là làm thế nào để tạo ra một lớp ngoại lệ tùy chỉnh Python không kế thừa Exception.

+0

Nhưng điều này không bọc một lớp C++ hiện có bắt nguồn từ std :: exception ... hoặc tôi thiếu một cái gì đó? Nếu tôi không, giải pháp của bạn không thực sự trả lời câu hỏi trong chủ đề này –

+0

@Dan Niero: Cách thông thường để "xuất" một ngoại lệ từ C++ sang Python không phải là để bọc nó, nhưng để dịch nó sang một ngoại lệ Python có nguồn gốc từ 'Ngoại lệ'. – user763305

+0

Tôi thấy quan điểm của bạn. Nhưng nếu nó là bên C++ nâng cao/ném một ngoại lệ, đó là giải pháp tốt nhất để bắt ngoại lệ đó trong Python? Trong ví dụ ở đây tôi có thể bắt một ngoại lệ được ném từ mã C++. Tuy nhiên, tôi không thể nêu ra ngoại lệ đó từ bên trong python. Tôi chỉ có thể bắt được nó. Nếu tôi không sai, trong giải pháp của bạn, bạn đưa ra một cách để nâng cao một ngoại lệ c + + từ python, nhưng nó không làm cho python "nhận thức" của ngoại lệ được nâng lên từ mã C++. Trên thực tế nó là, nhưng nó nghĩ rằng họ là tất cả RuntimeError. Xin lỗi nếu tôi thiếu điều gì đó, tôi chỉ đang cố gắng hiểu –

1

Nhờ mẫu variadic và chụp lambda tổng quát, chúng ta có thể sụp đổ Jack Edmond's answer vào một cái gì đó nhiều dễ quản lý hơn và ẩn tất cả các cruft từ người sử dụng:

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; 
} 

Để vạch trần MyCPPException như một ngoại lệ, bạn chỉ cần thay đổi py::class_ trong các ràng buộc để exception_:

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

Và bây giờ chúng ta trở lại với niceties của Boost.Python: không cần phải đặt tên cho cá thể class_, không cần thêm PyObject* và không cần thêm chức năng nào đó ở đâu đó.

+0

Tôi đã thử giải pháp của bạn và gặp lỗi sau ở phía Python: 'SystemError: exception không phải là lớp con BaseException' và' TypeError: các lớp bắt buộc không thừa kế từ BaseException không được phép'. Boost.Python V1.61, Python 3.4. –