I am successfully able to load a python script file and call a function within using boost::python in a C++ app.
In the boost python EmbeddingPython wiki there is a tip on how to load a python module.
namespace bp = boost::python;
bp::object import(const std::string& module, const std::string& path, bp::object& globals)
{
    bp::dict locals;
    locals["module_name"] = module;
    locals["path"]        = path;
    bp::exec("import imp\n"
             "new_module = imp.load_module(module_name, open(path), path, ('py', 'U', imp.PY_SOURCE))\n",
             globals,
             locals);
    return locals["new_module"];
}
I can successfully use this to import a python module (test.py)
int main()
{
    Py_Initialize();
    bp::object main    = bp::import("__main__");
    bp::object globals = main.attr("__dict__");
    bp::object module  = import("test", "test.py", globals);
    bp::object run     = module.attr("run");
    run();
    return 0;
}
Running the above code with a hello-world test.py script works fine:
test.py:
def run():
    print "hello world"
Output:
hello world
However, I now want to expose a C++ class to that script.
struct Foo
{
    void f() {}
};
As per the boost::python documentation, I expose this class as follows:
BOOST_PYTHON_MODULE(FooModule)
{
    bp::class_<Foo>("Foo")
        .def("f", &Foo::f)
        ;
}
As per the instructions in the above-linked wiki, I can then import my FooModule, and store it in my globals:
PyImport_AppendInittab("FooModule", &initFooModule); 
...
bp::object Foo = bp::import("FooModule");
globals["Foo"] = Foo;
This import is done prior to importing my test.py script, and this globals object is the one passed to bp::exec when importing my script (ie: Foo should be in the globals dict which bp::exec exposes to my script when importing).
However, for some reason my Foo module is not visible to test.py
How can I expose my Foo class to the test.py python script I am loading?
test.py:def run():
    foo = Foo()
    foo.f()
main.cpp:#include <iostream>
#include <boost/python.hpp>
namespace bp = boost::python;
bp::object import(const std::string& module, const std::string& path, bp::object& globals)
{
    bp::dict locals;
    locals["module_name"] = module;
    locals["path"]        = path;
    bp::exec("import imp\n"
             "new_module = imp.load_module(module_name, open(path), path, ('py', 'U', imp.PY_SOURCE))\n",
             globals,
             locals);
    return locals["new_module"];
}
struct Foo
{
    void f() {}
};
BOOST_PYTHON_MODULE(FooModule)
{
    bp::class_<Foo>("Foo")
        .def("f", &Foo::f)
        ;
}
int main()
try
{
    PyImport_AppendInittab("FooModule", &initFooModule);
    Py_Initialize();
    // get a handle to the globals dict    
    bp::object main = bp::import("__main__");
    bp::object globals = main.attr("__dict__");
    // import FooModule, and store it in the globals dict
    bp::object Foo = bp::import("FooModule");
    globals["Foo"] = Foo;
    // import the test script, passing the populated globals dict
    bp::object module = import("test", "test.py", globals);
    bp::object run = module.attr("run");
    // run the script
    run();
    return 0;
}
catch(const bp::error_already_set&)
{
    std::cerr << ">>> Error! Uncaught exception:\n";
    PyErr_Print();
    return 1;
}
Output:>>> Error! Uncaught exception: Traceback (most recent call last): File "test.py", line 2, in run foo = Foo() NameError: global name 'Foo' is not defined
Instead of trying to inject FooModule into the Python script from the C++ side, just register the module with PyImport_AppendInittab from the C++ side and then import it from the Python side:
import FooModule
def run():
    foo = FooModule.Foo()
    foo.f()
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With