Search code examples
c++python-2.7boost-python

Add function definition selectively to a python namespace


I want to controll which function is allowed to reach from a python code.

Here the struct, where I defined some functions for the Python to extend:

struct Worldb
{
    void messagewindow(std::string msg) { functions.Messagewindow(msg); }
    void setnumber(int value) { publicnumber=value; }
    string getnumber() { return functions.converttostring(publicnumber); }
    int publicnumber;
};

And here is the code ,where I add the definition to the code and send the Python code to the compiler:

Py_Initialize();

worldb.publicnumber = 1;

bp::object main_module = bp::import("__main__");
bp::object main_namespace = main_module.attr("__dict__");


main_namespace["Worldb"] = bp::class_<Worldb>("Worldb")
    .def("messagewindow", &Worldb::messagewindow)
    .def("setnumber", &Worldb::setnumber)
    .def("getnumber", &Worldb::getnumber);

main_namespace["cpp"] = bp::ptr(&worldb);//to prevent the worldb object copied 

bp::object compileit;
try
{
     compileit = exec(
         "cpp.messagewindow(cpp.getnumber())\n"
        "cpp.setnumber(8)\n",
        main_namespace);

}
catch(bp::error_already_set &)

I can extend functions into the Python manually with the .def easily, but I can't find any solution to put it in some kind of "if" statement, to check it is allowed to add to the python or not. Of course I can put each function into an unique namespace, but that is far from elegant and I think it's maybe waste some memory too.

Sorry for my bad english and thank you for any advice you give.


Solution

  • It is not necessary to have everything in one statement -- the ability to chain the calls is just a matter of convenience (def and other member functions return reference to the instance they were invoked on to let this happen).

    If we analyze the statement

    main_namespace["Worldb"] = bp::class_<Worldb>("Worldb")
        .def("messagewindow", &Worldb::messagewindow)
        .def("setnumber", &Worldb::setnumber)
        .def("getnumber", &Worldb::getnumber);
    

    we'll see that it performs the following functions in sequence:

    • Creates a new instance of class_<Worldb>
    • Calls its member function def to expose messagewindow
    • Calls its member function def to expose setnumber
    • Calls its member function def to expose getnumber
    • Assigns it to main_namespace["Worldb"].

    We can rewrite this to have each part as a separate statement in the following manner:

    {
        bp::class_<Worldb> test_binding = bp::class_<Worldb>("Worldb");
        test_binding.def("messagewindow", &Worldb::messagewindow);
        test_binding.def("setnumber", &Worldb::setnumber);
        test_binding.def("getnumber", &Worldb::getnumber);
    
        main_namespace["Worldb"] = test_binding;
    }
    

    Note: We introduce a new scope to limit the lifetime of test_binding, which is no longer needed after the assignment.

    Having done so, it is trivial to expose the individual methods conditionally.

    Sample Code

    #include <boost/python.hpp>
    
    namespace bp = boost::python;
    
    struct test
    {
        void one() {}
        void two() {}
        void three() {}
    };
    
    int main()
    {
        Py_Initialize();
    
        try {
            bp::object main_module = bp::import("__main__");
            bp::object main_namespace = main_module.attr("__dict__");
    
            // Simple bitmap of methods to expose:
            // * bit 0 -> one()
            // * bit 1 -> two()
            // * bit 2 -> three()
            uint32_t method_mask(5);
    
            {
                // Limit the scope of `test_binding` variable
                bp::class_<test> test_binding = bp::class_<test>("test");
                if ((method_mask & 1) == 1) {
                    test_binding.def("one", &test::one);
                }
                if ((method_mask & 2) == 2) {
                    test_binding.def("two", &test::two);
                }
                if ((method_mask & 4) == 4) {
                    test_binding.def("three", &test::three);
                }
    
                main_namespace["test"] = test_binding;
            }
    
            exec("print dir(test)\n", main_namespace);
        } catch (bp::error_already_set &) {
            PyErr_Print();
        }
    
        Py_Finalize();
    
        return 0;
    }
    

    Console Output

    Note: We expect one() and three() to be exposed. I re-formatted the output for better readability.

    ['__class__', '__delattr__', '__dict__', '__doc__', '__format__'
        , '__getattribute__', '__hash__', '__init__', '__instance_size__'
        , '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__'
        , '__sizeof__', '__str__', '__subclasshook__', '__weakref__'
        , 'one', 'three']
    

    References