5

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

Exposing a C++ class to python:

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

Question:

How can I expose my Foo class to the test.py python script I am loading?


Full working example:

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

1 Answer 1

4

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()
Sign up to request clarification or add additional context in comments.

1 Comment

Note to anyone else looking at this, in doing the above, bp::import("FooModule"); is no longer necessary. The call to PyImport_AppendInittab is all that is required to allow the script to import FooModule

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.