1

I want to be able to run Python scripts in my app to allow automating stuff and modifying existing objects/calling methods of existing objects.

In my application there is a BasicWindow class and MainWindow class that derives from the former. For now at application start I initialize one instance of MainWindow. This object has many functions, among them there is one that loads files (LoadFile()), and I will use it as example here.

Lets say that I want to call that particular function (but not limited to that function, it is just an example of the functionality that I want to achieve from Python) of that particular object instance.

This method is not a static one. For this I am using Boost.Python and I am creating a module this way:

BOOST_PYTHON_MODULE(MyModule)
{

    MainWindow::PythonExpose(); //not really sure how to operate here
    //more stuff
}

The idea is that I could call from Python something like:

MainWindow.LoadFile()

or even better, just:

LoadFile()

One solution could be to create static, application scoped functions and then just expose those functions. In C++ I could find the particular instance of MainWindow: (both methods are static)

void AppHelper::LoadFile()
{
    GetMainWindow()->LoadFile();
}


void AppHelper::PythonExposeGlobal()
{
    using namespace boost::python;
    def("LoadFile", &AppHelper::LoadFile);
}

Is it possible to achieve this? The general question would be: is it possible to call methods of existing objects (in C++) from Python? If so, how to do it? If not, what can I do to mimic this behavior?

For example, I could easily enable scripting capabilities in my C# application and sharing instances of existing objects. (But of course C# has reflection).

1
  • 2
    Yes, either the way you propose, or you expose the class (no constructor, just the functions you need), create a python object referring to existing instance (using a pointer -- e.g. bp::object(bp::ptr(&foo))) and then push that to the interpreter so your scripts can use it. Commented Aug 30, 2018 at 13:42

1 Answer 1

2

If you can guarantee that the object will live as long as any scripts using it run, then there's a fairly simple approach that I use.

I'll use a primitive counter class for demonstration:

class counter
{
public:
    counter() : count(0) {}

    void increment() { ++count; }

    int count;
};

Now, I expose this class to python, such that it considers it non-copyable, and doesn't allow construction of new instances. I also expose any members that I want to use from the scripts.

BOOST_PYTHON_MODULE(example)
{
    bp::class_<counter, boost::noncopyable>("Counter", bp::no_init)
        .def("increment", &counter::increment)
        ;
}

Next step is to create a Python object that uses an existing instance, and allow the script to use it (e.g. add it as an attribute of some module, such as the main one).

counter c;

bp::object main_module(bp::import("__main__"));

main_module.attr("c") = bp::object(bp::ptr(&c));

Now your scripts can use this instance:

c.increment()

Sample program:

#include <boost/python.hpp>
#include <iostream>

namespace bp = boost::python;

// Simple counter that can be incremented
class counter
{
public:
    counter() : count(0) {}

    void increment() { ++count; }

    int count;
};

// Expose the counter class to Python
// We don't need constructor, since we only intend to use instance
// already existing on the C++ side
BOOST_PYTHON_MODULE(example)
{
    bp::class_<counter, boost::noncopyable>("Counter", bp::no_init)
        .def("increment", &counter::increment)
        ;
}

int main()
{
    Py_InitializeEx(0);

    // Bind our class
    initexample();

    counter c;

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

    // Add the current instance of counter to Python as attribute c of the main module
    main_module.attr("c") = bp::object(bp::ptr(&c));

    std::cout << "Before: " <<  c.count << '\n';

    // Increment the counter from Python side
    bp::exec("c.increment()", main_namespace);

    std::cout << "After: " << c.count << '\n';

    Py_Finalize();
    return 0;
}

Console Output:

Before: 0
After: 1
Sign up to request clarification or add additional context in comments.

Comments

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.