Вопрос

I have an in-house library written in C++ that I'm currently working on extending into Python. I started this task with Boost.Python in mind, but I'm open to alternatives.

Currently I have a C++ function that needs to accept a Python class instance, and then use the methods of this object to perform certain tasks. The idea is for the Python user to never need to deal with C++. They're expected to create this Python object from a Python template/example class that I will provide, with preset method names which I can assume to be there in my C++ library.

The interface exposed to the Python user looks like:

class Foo(object):

    def __init__(self, args):
        """create an instance of this class with instance-specific attributes"""

    def Bar1(self, a, b, c):
        """do something with the given integers a, b and c"""
        pass

    def Bar2(self, a, b, c):
        """do something else with the given integers a, b and c"""
        pass

import mylib

cheese = mylib.Wine()
cheese.do_something(Foo)

In C++, the corresponding code looks like:

#include <boost/python.h>
#include <Python.h>

class Wine {

public:  

    Wine() {};

    ~Wine() {};

    static void do_something(boost::python::object *Foo) {

        int a = 1;
        int b = 2;
        int c = 3;

        Foo->attr("Bar1")(a, b, c);
        Foo->attr("Bar2")(a, b, c);
    };
};

BOOST_PYTHON_MODULE(mylib)
{
    using namespace boost::python;
    class_<Wine>("Wine")
        .def("do_something", &Wine::do_something);
};

I have successfully compiled this code and verified that the C++ class called Wine is really exposed to Python and I can access its member functions. If I write a member function called "greet()" that only returns "Hello, world!", it works perfectly.

I need to stress here the importance of passing an instance of Foo. I do not have the luxury to simply import the Foo module into C++ code and create an instance of Foo in C++. The object that I want to receive from the Python user has attributes I need to use that is specific to the instance, and not to the class itself.

Problem is that I can't figure out how to pass a Python instance into do_something, such that it will appear in C++ as a callable boost::python::object. The above code returns the following C++ signature mismatch error:

Boost.Python.ArgumentError: Python argument types in
    Wine.do_something(Wine, Foo)
did not match C++ signature:
    do_something(boost::python::api::object*)

Two days of perusing the internet for answers has yielded no progress. There seems to be a wealth of information on how to pass C++ classes into Python, but I was unable to find information on doing this in the opposite direction. Would really appreciate some guidance here.

Thanks!

Это было полезно?

Решение

There are two errors in the initial code:

  • Boost.Python is attempting to pass two arguments (self and an instance of Foo) to the static Wine::do_something() C++ function that only accepts one argument. To resolve this, when exposing the Wine class, the Python Wine.do_something() member function needs to be set as static via the boost::python::class_::staticmethod() member function. When exposed as a static method, Boost.Python will no longer pass the self instance argument.
  • Unlike the Python/C API, where pointers are often used as handles to objects (PyObject*), Boost.Python provides a higher-level notation boost::python::object class that is often passed around by value or reference. Internally, this class interacts with a boost::python::handle that performs smart pointer management for PyObject.

Here is a complete Python extension based on the original code:

#include <boost/python.hpp>

class Wine
{
public:  

  static void do_something(boost::python::object object)
  {
    int a = 1;
    int b = 2;
    int c = 3;

    object.attr("Bar1")(a, b, c);
    object.attr("Bar2")(a, b, c);
  };
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<Wine>("Wine")
    .def("do_something", &Wine::do_something)
      .staticmethod("do_something")
    ;
};

Interactive usage:

>>> class Foo(object):
...     def Bar1(self, a, b, c):
...         print "Bar1", locals()
...     def Bar2(self, a, b, c):
...         print "Bar2", locals()
... 
>>> import example
>>> cheese = example.Wine()
>>> cheese.do_something(Foo())
Bar1 {'a': 1, 'c': 3, 'b': 2, 'self': <__main__.Foo object at 0xb6b0f2ac>}
Bar2 {'a': 1, 'c': 3, 'b': 2, 'self': <__main__.Foo object at 0xb6b0f2ac>}

Другие советы

To expose a method which accepts a Python object as an argument you should use boost::python::object not boost::python::object *

void do_something(boost::python::object Foo)

To expose a static method expose it like a regular function: def("do_something", Wine::do_something);

import mylib

# method
cheese = mylib.Wine()
foo = Foo()
cheese.do_something(foo)

#static method
foo = Foo()
mylib.do_something(foo)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top