2012-12-05 34 views
5

Vì vậy, tôi đang nghiên cứu một dự án nhỏ trong đó tôi đang sử dụng Python như một công cụ tạo tập lệnh nhúng. Cho đến nay tôi đã không có nhiều rắc rối với nó bằng cách sử dụng boost.python, nhưng có một cái gì đó tôi muốn làm gì với nó nếu nó có thể.Boost.Python - Tăng tốc độ :: python :: đối tượng làm đối số cho hàm python?

Về cơ bản, Python có thể được sử dụng để mở rộng các lớp C++ của tôi bằng cách thêm các hàm và thậm chí cả các giá trị dữ liệu cho lớp. Tôi muốn để có thể có những tồn tại ở bên C++, do đó, một hàm python có thể thêm các thành viên dữ liệu vào một lớp, và sau đó cùng một cá thể được truyền cho một hàm khác sẽ vẫn có chúng. Mục tiêu ở đây là viết một công cụ cốt lõi chung trong C++ và cho phép người dùng mở rộng nó bằng Python theo bất kỳ cách nào họ cần mà không cần phải chạm vào C++. Vì vậy, những gì tôi nghĩ sẽ làm việc là tôi sẽ lưu trữ một boost::python::object trong lớp C++ như một giá trị self, và khi gọi python từ C++, tôi sẽ gửi đối tượng python đó thông qua boost::python::ptr(), để sửa đổi trên phía python sẽ tồn tại trở lại lớp C++. Thật không may khi tôi cố gắng này, tôi nhận được lỗi sau:

TypeError: No to_python (by-value) converter found for C++ type: boost::python::api::object

Có cách nào đi qua một đối tượng trực tiếp đến một chức năng python như thế, hoặc bất kỳ cách nào khác tôi có thể đi về vấn đề này để đạt được mong muốn của tôi kết quả?

Cảm ơn trước vì đã được trợ giúp. :)

+0

Tìm thấy một giải pháp.Trong python, tôi đã thêm một hàm: def ProcessEvent (event, obj): return globals() [event] (obj.PyObj) Trong C++, tôi đã thêm một 'boost :: python :: object' đặt tên là 'PyObj' vào lớp C++ của tôi, được khởi tạo thành' boost :: python :: ptr (this) ', và gọi các sự kiện của tôi với nó, chuyển tên của sự kiện mà tôi muốn gọi làm tham số đầu tiên và' boost :: python :: ptr' vào đối tượng mà tôi muốn chuyển cho nó như là thứ hai. Điều đó hoạt động như tôi đã hy vọng - khi tôi thêm một thuộc tính vào một sự kiện, nó vẫn còn đó khi tôi truyền nó cho người khác. Có lẽ không phải là giải pháp tốt nhất ... Nhưng, nó hoạt động. –

Trả lời

5

Có giải pháp tuyệt vời này từ danh sách gửi thư C++ sig.

Triển khai std::map<std::string, boost::python::object> trong lớp C++, sau đó quá tải __getattr__()__setattr__() để đọc và ghi vào std :: map đó. Sau đó, chỉ cần gửi nó đến trăn với boost::python::ptr() như thường lệ, không cần phải giữ một đối tượng xung quanh trên mặt C++ hoặc gửi một đến python. Nó hoạt động hoàn hảo.

Chỉnh sửa: Tôi cũng thấy rằng tôi phải ghi đè chức năng __setattr__() theo cách đặc biệt vì nó đã phá vỡ những thứ tôi đã thêm với add_property(). Những điều đó làm việc tốt khi nhận được chúng, vì python kiểm tra các thuộc tính của lớp trước khi gọi __getattr__(), nhưng không có kiểm tra nào như vậy với __setattr__(). Nó chỉ gọi nó trực tiếp. Vì vậy, tôi đã phải thực hiện một số thay đổi để biến điều này thành một giải pháp đầy đủ. Dưới đây là thực hiện đầy đủ các giải pháp:

Đầu tiên tạo một biến toàn cầu:

boost::python::object PyMyModule_global; 

Tạo một lớp như sau (với bất kỳ thông tin khác mà bạn muốn thêm vào nó):

class MyClass 
{ 
public: 
    //Python checks the class attributes before it calls __getattr__ so we don't have to do anything special here. 
    boost::python::object Py_GetAttr(std::string str) 
    { 
     if(dict.find(str) == dict.end()) 
     { 
     PyErr_SetString(PyExc_AttributeError, JFormat::format("MyClass instance has no attribute '{0}'", str).c_str()); 
     throw boost::python::error_already_set(); 
     } 
     return dict[str]; 
    } 

    //However, with __setattr__, python doesn't do anything with the class attributes first, it just calls __setattr__. 
    //Which means anything that's been defined as a class attribute won't be modified here - including things set with 
    //add_property(), def_readwrite(), etc. 
    void Py_SetAttr(std::string str, boost::python::object val) 
    { 
     try 
     { 
     //First we check to see if the class has an attribute by this name. 
     boost::python::object obj = PyMyModule_global["MyClass"].attr(str.c_str()); 
     //If so, we call the old cached __setattr__ function. 
     PyMyModule_global["MyClass"].attr("__setattr_old__")(ptr(this), str, val); 
     } 
     catch(boost::python::error_already_set &e) 
     { 
     //If it threw an exception, that means that there is no such attribute. 
     //Put it on the persistent dict. 
     PyErr_Clear(); 
     dict[str] = val; 
     } 
    } 
private: 
    std::map<std::string, boost::python::object> dict; 
}; 

Sau đó, xác định mô-đun python như sau, thêm bất kỳ lỗi và thuộc tính nào bạn muốn:

BOOST_PYTHON_MODULE(MyModule) 
{ 
    boost::python::class_<MyClass>("MyClass", boost::python::no_init) 
     .def("__getattr__", &MyClass::Py_GetAttr) 
     .def("__setattr_new__", &MyClass::Py_SetAttr); 
} 

Sau đó khởi python:

void PyInit() 
{ 
    //Initialize module 
    PyImport_AppendInittab("MyModule", &initMyModule); 
    //Initialize Python 
    Py_Initialize(); 

    //Grab __main__ and its globals 
    boost::python::object main = boost::python::import("__main__"); 
    boost::python::object global = main.attr("__dict__"); 

    //Import the module and grab its globals 
    boost::python::object PyMyModule = boost::python::import("MyModule"); 
    global["MyModule"] = PyMyModule; 
    PyMyModule_global = PyMyModule.attr("__dict__"); 

    //Overload MyClass's setattr, so that it will work with already defined attributes while persisting new ones 
    PyMyModule_global["MyClass"].attr("__setattr_old__") = PyMyModule_global["MyClass"].attr("__setattr__"); 
    PyMyModule_global["MyClass"].attr("__setattr__") = PyMyModule_global["MyClass"].attr("__setattr_new__"); 
} 

Một khi bạn đã làm tất cả những điều này, bạn sẽ có thể giữ lại thay đổi đến dụ thực hiện trong python trên vào thư mục C++. Bất kỳ thứ gì được định nghĩa trong C++ dưới dạng thuộc tính sẽ được xử lý đúng cách và bất kỳ thứ gì không được thêm vào dict thay vì của lớp là __dict__.

+0

Tuyệt vời! Cám ơn vì đã chia sẻ! – mrmclovin

Các vấn đề liên quan