7

Suppose I have a Python class like so:

class MyPythonClass:
    def Func1(self, param):
        return
    def Func2(self, strParam):
        return strParam

If I want to embed the Python script that contains that class in my C++ code, create an instance of that object via my C++ code, and then call a member on that python object, how do I go about that?

I would think it would be something like this:

namespace python = boost::python;
python::object main = python::import("main");
python::object mainNamespace = main.attr("__dict__");
python::object script = python::exec_file(path_to_my_script, mainNamespace);
python::object foo = mainNamespace.attr("MyPythonClass")();
python::str func2return = foo.attr("Func2")("hola");
assert(func2return == "hola");

But the many variations of this code I have tried have not worked. What is the magic sauce I need to pour over my code to be able to do this?

2 Answers 2

5

This is what ultimately worked for me.

namespace python = boost::python;
python::object main = python::import("main");
python::object mainNamespace = main.attr("__dict__");

//add the contents of the script to the global namespace
python::object script = python::exec_file(path_to_my_script, mainNamespace);

//add an instance of the object to the global namespace
python::exec("foo = MyPythonClass()", mainNamespace);
//create boost::python::object that refers to the created object
python::object foo = main.attr("foo");

//call Func2 on the python::object via attr
//then extract the result into a const char* and assign it to a std::string
//the last bit could be done on multiple lines with more intermediate variables if desired
const std::string func2return = python::extract<const char*>(foo.attr("Func2")("hola"));
assert(func2return == "hola");

Feel free to comment if there is a better way.

Sign up to request clarification or add additional context in comments.

2 Comments

This didn't really work for me (using Python 3.6, so new-style classes only). That said, this worked perfectly.
Thanks for sharing! Yes, I should probably update this question with a new answer, as I've since discovered better ways. That blog nicely encapsulates the whole process in one place.
1

Below is an example using the Boost::python embedding. I particularly like this approach since using the python class functions is as easy as if one was in python. However, it does take some setting up.

#include <boost/python.hpp>
// An abstract base class
class Base : public boost::noncopyable
{
public:
  virtual ~Base() {};
  virtual void func1() = 0;
  virtual std::string func2(std::string strParam) = 0;
}


// Boost.Python wrapper class for Base
// a function pointer to Base::hello and then calls function
struct BaseWrap : Base, python::wrapper<Base>
{

  virtual void func1()
  {
    this->get_override("func1")();
  }
  virtual void func2(std::string strParam)
  {
    std::string str = this->get_override("func2")(strParam);
    return str;
  }
}


// Pack the Base class wrapper into a module
BOOST_PYTHON_MODULE(embedded_py)
{
  //creating p c++ class instance called "base" and constructed with "Base"
  python::class_<BaseWrap, boost::noncopyable> base("Base");
}

int main()
{
  Py_Initialize();
  std::cout << "registering extension module embedded_py ..." << std::endl;

  // Register the module with the interpreter
  if (PyImport_AppendInittab("embedded_pi", initembedded_pi) == -1)
    throw std::runtime_error("Failed to add embedded_pi to the interpreter's "
                 "builtin modules");

  std::cout << "defining Python class derived from Base..." << std::endl;

  // Retrieve the main module
  python::object main = python::import("__main__");

  // Retrieve the main module's namespace
  python::object global(main.attr("__dict__"));

  // Load the python file
  std::string file = "YOUR_PYTHON_FILE.py";
  try{python::exec_file(file.c_str(),global,global);
  }catch(boost::python::error_already_set const &){
        // Parse and output the exception
        PyErr_Print();
        }

   // pull the python class from global
   // This is the name of MyPythonClass
   python::object LOT = global["LOT"];

   // Now creating and using instances of the Python class is almost easy
   python::object lot_base = LOT();
   Base& lot = python::extract<Base&>(lot_base) BOOST_EXTRACT_WORKAROUND;


  try{lot.func1();
  }catch(boost::python::error_already_set const &){
        PyErr_Print();
  }
  try{std::string hola = lot.func2("hello");
  }catch(boost::python::error_already_set const &){
        PyErr_Print();
  }

  Py_Finalize();

  return 1;
}

1 Comment

I just noticed that Adam Palunuik commented above with a similar suggestion.

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.