Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ class not recognized by Python 3 as a module via Boost.Python Embedding

The following example from Boost.Python v1.56 shows how to embed the Python 3.4.2 interpreter into your own application. Unfortunately that example does not work out of the box on my configuration with MSVC2013 under Windows 8.1. And I have not found 1 working complete example about embedding, at least none that is younger than 10 years or so.

I receive the following error running it: ImportError: 'embedded_hello' is not a built-in module

The code is here: http://pastebin.com/shTtdxT8

Any hints what I can do to let this run? And in general how to expose a c++ class in Python and vice versa?

like image 476
patlecat Avatar asked Nov 06 '14 19:11

patlecat


1 Answers

The code is compiling with a Python 2 header configuration. When compiling with a Python 3 header configuration, the boost/python/module_init.hpp would have declared the embedded_hello module's initialization function as PyInit_embedded_hello rather than initembedded_hello. I highly recommend verifying the proper header configuration, and performing a clean build of Boost.Python, as Boost.Python and modules built with the library need to use the same header configuration.

Additionally, when adding modules to the built-in table, the PyImport_AppendInittab() calls need to occur before Py_Initialize(). The PyImport_AppendInittab() documentation explicitly states:

Add a single module to the existing table of built-in modules. ... This should be called before Py_Initialize().

Boost.Python uses the BOOST_PYTHON_MODULE macro to define a Python module. Within the body of the module, the current scope is the module itself. Thus, when C++ types are exposed via type wrappers, such as when a C++ classes is exposed to Python via boost::python::class_, the resulting Python class will be within the module defined by BOOST_PYTHON_MODULE.

On the other hand, user-defined types declared in Python are first-class objects. From a C++ perspective, they can be treated as though they are a factory function. Hence, to use a Python defined class in C++, one needs to get a handle to the class object, then instantiate an instance of a class by calling the class object.


Here is a complete minimal example demonstrating embedding a Python 3 interpreter that:

  • Imports a module (example) that has been built directly into the binary and exposes a basic C++ class (spam_wrap) to Python (example.Spam) that has virtual function/dispatching with a default.
  • Demonstrates using the exposed Python class (example.Spam).
  • Derives from the exposed Python class (example.Spam) within Python (example.PySpam) and uses the resulting class.
#include <iostream>
#include <boost/python.hpp>

/// @brief Mockup Spam model.
struct spam
  : boost::noncopyable
{
  virtual ~spam() {};
  virtual std::string hello() { return "Hello from C++"; }
};

//@ brief Mockup Spam wrapper.
struct spam_wrap
  : spam,
    boost::python::wrapper<spam>
{
  virtual std::string hello()
  {
#if BOOST_WORKAROUND(BOOST_MSVC, <= 1300)
    return boost::python::call<std::string>(
      this->get_override("hello").ptr());
#else
    return this->get_override("hello")();
#endif
  }

  std::string default_hello() { return this->spam::hello(); }
};

/// @brief Python example module.
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose C++ spam_wrap as Python Spam class.
  python::class_<spam_wrap, boost::noncopyable>("Spam")
    .def("hello", &spam::hello, &spam_wrap::default_hello)
    ;
}   

int main()
{
  // Add example to built-in.
  PyImport_AppendInittab("example", &PyInit_example);

  // Start the interpreter.
  Py_Initialize();

  namespace python = boost::python;
  try
  {
    python::object main = python::import("__main__");
    python::object global = main.attr("__dict__");

    // Execute Python code, using the example module.
    exec(
      "from example import Spam          \n"
      "spam = Spam()                     \n"
      "                                  \n"
      "class PySpam(Spam):               \n"
      "    def hello(self):              \n"
      "        return 'Hello from Python'\n",     
      global, global);

    /// Check the instance of the Python object using the C++ class.
    // >>> spam_object = spam
    python::object spam_object = global["spam"];
    assert(python::extract<spam>(spam_object).check());
    // >>> result = spam_object.hello()
    python::object result = spam_object.attr("hello")();
    // >>> print(result)
    std::cout << python::extract<std::string>(result)() << std::endl;
    // >>> assert("Hello from C++" == result)
    assert("Hello from C++" == python::extract<std::string>(result)());

    /// Create an instance using PySpam class.  It too is a Python object.
    // >>> py_spam_type = PySpam
    python::object py_spam_type = global["PySpam"];
    // >>> py_spam_object = py_spam_type()
    python::object py_spam_object = py_spam_type();
    // >>> result = py_spam_object()
    result = py_spam_object.attr("hello")();
    // >>> print(result)
    std::cout << python::extract<std::string>(result)() << std::endl;
    // >>> assert("Hello from Python" == result)
    assert("Hello from Python" == python::extract<std::string>(result)());
  }
  catch (const python::error_already_set&)
  {
    PyErr_Print();
  }
}

The program should run to completion without errors, resulting in the following output:

Hello from C++
Hello from Python
like image 121
Tanner Sansbury Avatar answered Nov 18 '22 16:11

Tanner Sansbury